with the flow

WEBプログラマを目指すWEBデザイナーが書き綴る開発日誌のようなもの

input type="file"をCSS3で装飾 改善版 IE7〜対応。

すごく昔に書いた、input type="file"をCSSとJavaScriptで綺麗に装飾するものの改善版を作りました。

Chrome23.0.1, Firefox17, Opera12, IE9,8,7で正常に表示されるのを確認。

■内容
・「参照」ボタンを押しても、textboxを押しても参照ダイアログが起動。
・ファイルを参照するとtextbox内にファイル名が入る。ファイル名が長い場合は「...」と省略される。
・「アップロード」ボタンがあった場合で作ってみた。押しても何も起こりませんが。
・画面内に複数入れたい場合は、"uploader"を複数入れることで対応可能。
JavaScriptが無効な状態にも対応。無効な環境では普通のブラウザデフォルトのinput type="file"が出現し、  アップロードボタンの左に並びます。
IEは画像作ってないので必要であれば足してください。 ※IEのfilterでグラデ作ろうかとも思ったけど、あんまりすきじゃないので使ってません。

■ポイント
IE対策。trigger()で、潰して見えなくしたinput=fileをJavaScript経由でクリックさせる方法は IEには通用しないので(無効にされる)、力技でtextboxの上に透明にして重ねる方法を取りました。 ただ、IEのデフォルトのinput=fileは、

・参照したファイル名が入るテキストbox部分をクリックしても参照ダイアログが立ち上がらない。
・中身のテキストも修正可能

という罠があるので、font-sizeを大きくすることで「参照...」と書いてあるボタンのサイズを調整、 overflow: hiddenでカットという荒業で乗り切りました。
フェイクのinput要素(".text")は、 contenteditable="false" readonly="true"を付けることでタブキーなどでのフォーカスをお断りしています。

HTML

<div class="uploader no-js">
	<input type="file" size="25">
	<div class="fakeFile" contenteditable="false" readonly="true">
		<p class="text"></p>
		<a href="javascript: void 0;" class="uiBtn browse" title="参照ボタンをクリックし写真選択後、アップロードボタンを押してください。">参照...</a>
	</div>
	<a href="javascript:void 0;" id="upload" class="uiBtn upload">アップロード</a>
</div>

CSS

body {
    background: #272727;
    font-size: 12px;
    font-family: "メイリオ",Meiryo,"MS Pゴシック","MS PGothic",Tahoma,Verdana,Arial,'Hiragino Kaku Gothic Pro',sans-serif;
}
.uploader {
	position: absolute;
    top: 50%;
    left: 50%;
    width: 402px;
    margin: -15px 0 0 -200px;
}

/* // Fake file uploader
------------------------- */
.fakeFile {
	position: relative;
	float: left;
	height: 25px;
	overflow: visible;
	cursor: pointer;
	z-index: 1;
}
.no-js .fakeFile { display: none; }
.fakeFile .text {
	float: left;
	width: 190px;
	height: 19px;
	margin: 1px 10px 0 0;
	padding: 1px 1px 1px 5px;
	border: 1px solid #000;
	border-radius: 2px;
	background: #161616;
	color: #ccc;
	line-height: 20px;
	white-space: nowrap;
	text-shadow: 0 0 -1px #333;
	overflow: hidden;
	text-overflow: ellipsis;
	cursor: pointer;
	box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5), 1px 1px 0 rgba(255,255,255,0.1);
}
.uploader input[type="file"] {
	position: absolute;
	display: block;
	border: 0;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	z-index: 1;
}
.uploader.isIE input[type="file"] {
	position: absolute;
	left: 0;
	top: 0;
	width: 281px;
	height: 26px;
	margin: 0;
	font-size: 52px;
	opacity: .0;
	filter: alpha(opacity=0);
	-ms-filter: "alpha(opacity=0)";
	cursor: pointer;
	z-index: 2;
}
.uploader.no-js input[type="file"] { width: auto; height: auto; display: inline; }
/* IE7 */
*:first-child+html .uploader input[type="file"] { left: -7px; font-size: 72px; }

/* // UI Parts: button
------------------------- */
.uiBtn:link, .uiBtn:visited {
    display: inline-block;
	padding: 6px 0;
	border: 1px solid #070a0d;
	border-radius: 2px;
	background: #363636;
	background: -moz-linear-gradient(top, #444, #262626);
	background: -webkit-linear-gradient(top, #444, #262626);
	background: -o-linear-gradient(top, #444, #262626);
	background: linear-gradient(to bottom, #444, #262626);
	color: #ddd;
	text-align: center;
	text-decoration: none;
    text-shadow: 0 -1px 0 #111;
	box-shadow: inset 0 0 2px rgba(100,100,100,0.5), 1px 1px 0 rgba(0,0,0,0.15);
    overflow: hidden;
    zoom: 1;
}
.uiBtn:hover, .uiBtn.hover {
	background: #444;
	background: -moz-linear-gradient(top, #525252, #2e2e2e);
	background: -webkit-linear-gradient(top, #525252, #2e2e2e);
	background: -o-linear-gradient(top, #525252, #2e2e2e);
	background: linear-gradient(to bottom, #525252, #2e2f30);
	color: #fff;
}
.uiBtn:active, .uiBtn.active {
	border-color: #000;
	background: #111;
	background: -moz-linear-gradient(top, #262626, #444);
	background: -webkit-linear-gradient(top, #262626, #444);
	background: -o-linear-gradient(top, #262626, #444);
	background: linear-gradient(to bottom, #262626, #444);
	color: #999;
	box-shadow: inset 1px 1px 1px rgba(0,0,0,0.3), 0 1px 0 rgba(255,255,255,0.1);
}

/* uiBtn overwrite  */
a:link.browse, a:visited.browse,
a:link.upload, a:visited.upload {
	display: block;
	float: left!important;
	width: 70px;
	height: 11px!important;
	line-height: 12px;
	border-radius: 0;
}
a:link.browse, a:visited.browse {
	border-top-left-radius: 2px;
	border-bottom-left-radius: 2px;
}
a:link.upload, a:visited.upload {
	width: 120px;
	margin-left: -1px;
	border-left-color: #161616;
	border-top-right-radius: 2px;
	border-bottom-right-radius: 2px;
	box-shadow: inset 0 0 2px rgba(100,100,100,0.5), inset 1px 0 0 rgba(150,150,150,0.3), 1px 1px 0 rgba(0,0,0,0.15);
}
a:active.upload { box-shadow: inset 1px 1px 1px rgba(0,0,0,0.3),1px 1px 0 rgba(255,255,255,0.1); }
.no-js a:link.upload, .no-js a:visited.upload {
    display: inline-block;
    float: none!important;
    border-radius: 2px;
    box-shadow: inset 0 0 2px rgba(100,100,100,0.5), 1px 1px 0 rgba(0,0,0,0.15);
}

JavaScript

$(function(){
    $(".fakeFile").each(function(){
        var $this = $(this),
            $browse = $this.children(".browse"),
            $file = $this.prev("input");
        
        //JavaScript無効の状態ではno-jsを取る。Modernizrを入れていて<html>に".no-js"が付けている環境では、この記述は不要。
        $this.parent().removeClass("no-js");
        
        //IEはtrigger()でのクリックが効かないので対策。IE判定は横着してます--;
        if(/*@cc_on!@*/false) {
            $file
                .parent().addClass("isIE")
                .end()
                .bind({
                    click: function(e){ $(this).blur(); },
                    mousedown: function(){ $browse.addClass("active"); },
                    mouseup: function(){ $browse.removeClass("active"); },
                    mouseover: function(){ $browse.addClass("hover").tipsy("show"); },
                    mouseout: function(){ $browse.removeClass("hover active").tipsy("hide"); },
                    change: function(){ $(this).next().children(".text").text($(this).val()); }
                });
        } else {
            //IE以外のブラウザではtriggerで。
            $this.bind({
                click: function(e){ $file.trigger("click"); },
                mousedown: function(){ $browse.addClass("active"); },
                mouseup: function(){ $browse.removeClass("active"); },
                mouseover: function(){ $browse.addClass("hover").tipsy("show"); },
                mouseout: function(){ $browse.removeClass("hover active").tipsy("hide"); }
            });
            $file.change(function(){
                $this.children(".text").text($(this).val());
            });
        }
    });
});