CSSとJSでHTMLフォームのファイル入力を柔軟にデザインする方法

HTMLのフォームコントロール要素 (input要素, button要素, select要素, textarea要素, etc…) には、ブラウザ毎に異なったデザインが適用されていて、全てのブラウザで共通のデザインを適用するのは、割とハードルが高めです。

特に、ファイル入力 (input[type=”file”]) は柔軟にデザインするのが困難です。今回はJSを使用してファイル入力を柔軟にデザインする方法をご紹介します。

ファイル入力のHTMLコード

<form>
  <p><input type="file" id="file" name="file" /></p>
  <p><input type="reset" /></p>
</form>

とてもシンプルなファイル入力のHTMLコードです。id属性だけは忘れずに指定しておく必要があります。フォーム (form要素) やリセットボタン (input[type=”reset”]要素) は必要という訳ではありません。

ファイル入力のJSコード

<script>
!function( window, document ) {
  Array.prototype.forEach.call( document.body.querySelectorAll( '[type="file"]' ), function( file ) {
    var label = document.createElement( 'label' );
    label.setAttribute( 'for', file.getAttribute( 'id' ) );
    label.setAttribute( 'class', file.getAttribute( 'class' ) );
    label.classList.add( 'file-label' );
    label.setAttribute( 'tabindex', 0 );
    label.setAttribute( 'data-file', file.value.replace( /^.*[\\\/]/, '' ) );
    file.setAttribute( 'class', 'file-input' );
    file.setAttribute( 'tabindex', -1 );
    file.parentNode.insertBefore( label, file.nextElementSibling );
    file.addEventListener( 'change', function( event ) {
      label.setAttribute( 'data-file', this.value.replace( /^.*[\\\/]/, '' ) );
    }, false );
  } );
  Array.prototype.forEach.call( document.body.querySelectorAll( '[type="reset"]' ), function( button ) {
    button.addEventListener( 'click', function( event ) {
      Array.prototype.forEach.call( this.form.querySelectorAll( '[data-file]' ), function( file ) {
        file.setAttribute( 'data-file', '' );
      } );
    }, false );
  } );
} ( window, document );
</script>

script要素に直書きすると上記のようになります。jQueryなどのJSライブラリに依存しないネイティブなJSコードなので、body終了タグ (</body>) の前あたりにコピペすれば、すぐに使用できます。

JSコード前半部分の解説

JSコードの前半部分には、ファイル入力の後に空のラベルを追加する処理が書かれています。

Array.prototype.forEach.call( document.body.querySelectorAll( '[type="file"]' ), function( file ) {
  var label = document.createElement( 'label' );
  label.setAttribute( 'for', file.getAttribute( 'id' ) );
  label.setAttribute( 'class', file.getAttribute( 'class' ) );
  label.classList.add( 'file-label' );
  label.setAttribute( 'tabindex', 0 );
  label.setAttribute( 'data-file', file.value.replace( /^.*[\\\/]/, '' ) );
  file.setAttribute( 'class', 'file-input' );
  file.setAttribute( 'tabindex', -1 );
  file.parentNode.insertBefore( label, file.nextElementSibling );
  file.addEventListener( 'change', function( event ) {
    label.setAttribute( 'data-file', this.value.replace( /^.*[\\\/]/, '' ) );
  }, false );
} );

このJSコードによって、DOM上のHTML構造は下記のようになります。

<form>
  <p><input type="file" id="file" name="file" class="file-input" /><label for="file" class="file-label" data-file=""></label></p>
  <p><input type="reset" /></p>
</form>

「file-input」というclass属性値がファイル入力に追加され、「file-label」というclass属性値を持つラベルが追加されます。ちなみに、ファイル入力にclass属性値が設定されていた場合、ラベルのclass属性値に置き換えられます。

JSコード適用前
<input ... class="example-class" />
JSコード適用後
<input ... class="file-input" /><label ... class="example-class file-label" ...></label>

ファイル入力のid属性値 ([id=”example-id”]) がラベルのfor属性値 ([for=”example-id”]) に設定されて関連付けられるので、ラベルをクリックするとファイル入力が起動するようになります。

ファイルが選択されると、ラベルのdata-file属性値にファイル名が入るようになっています ([data-file=”ファイル名”])。

これでファイル入力をデザインするための準備は完了です。

JSコード後半部分の解説

JSコードの後半部分には、フォームのリセットボタンが押された場合の処理が書かれています。

Array.prototype.forEach.call( document.body.querySelectorAll( '[type="reset"]' ), function( button ) {
  button.addEventListener( 'click', function( event ) {
    Array.prototype.forEach.call( this.form.querySelectorAll( '[data-file]' ), function( file ) {
      file.setAttribute( 'data-file', '' );
    } );
  }, false );
} );

リセットボタンが押されるとファイル入力はリセットされますが、data-file属性に設定されたファイル名まではリセットされません。そこで、上記のJSコードによってdata-file属性値をリセットします。

リセットボタンを使用しないのであれば、この部分は必要ありません。

ファイル入力のCSSコード

<style>
.file-input{
  clip:rect(1px,1px,1px,1px);
  clip-path:inset(50%);
  position:fixed;
  top:0;
}
.file-label{
  border:solid 1px;
  box-sizing:border-box;
  display:inline-block;
  line-height:1.5;
  padding:0.5em;
  outline:none;
  overflow:hidden;
  text-overflow:ellipsis;
  vertical-align:middle;
  white-space:nowrap;
  max-width:100%;
}
.file-label:before{
  content:"Choose File";
}
.file-label:lang(ja):before{
  content:"ファイルを選択";
}
.file-label[data-file]:not([data-file=""]):before{
  content:attr(data-file);
}
.file-input:enabled+.file-label{
  cursor:pointer;
}
.file-input:enabled+.file-label:not(:hover):not(:focus){
  border-color:#808080;
}
.file-input:disabled+.file-label{
  opacity:0.5;
}
</style>

上記のCSSコードは、テキスト入力 (input[type=”text”]要素) っぽいデザインにするための設定です。ポイントとなる部分は次のとおり。

  • JSコードによって生成されたclass属性値の「file-input」と「file-label」をセレクタの基点とする。
  • file-input (ファイル入力) を非表示にする。
  • file-label (ラベル) に設定されたdata-file属性値を疑似要素で表示させる。
  • file-label (ラベル) をファイル入力の代わりにデザインする。

「CodePen」による実際の表示

See the Pen Design of file input by nov (@numerofive) on CodePen.

今回紹介した、JSで生成した要素のdata属性を疑似要素で表示して、ファイル入力の代わりにデザインするという方法は、スタイルシートやJavaScriptが無効の環境では余計なものを表示させないということに配慮したためです。

それと同時に、様々なデザインを適用できる柔軟性がありますので、作る要件に応じて活用・応用してみてください。

関連記事