TOP

【Vue.js】selectタグのデザインをカスタマイズ

【Vue.js】selectタグのデザインをカスタマイズ

先日、jQueryを使ってselectタグをカスタマイズする方法をブログに書いたんですが、JSが長いし、なんかややこしいし、ということでVue.jsで作り直してみました。

demo

実装方法

HTML

<div id="select">
  <div class="selectBox">

<!-- カスタマイズ用のdivタグ -->
    <div class="selectBox__output" v-on:click="selectorshow=!selectorshow" v-bind:class="[ selectorshow ? 'open' : '' ]">
      <span v-for="selector in selectors" v-show="selected == selector.value" v-bind:key="selector.id">{{selector.text}}</span>
    </div>
    <transition name="accodion">
    <div class="selectBox__selector" v-show="selectorshow">
      <div class="selectBox__selectorItem" v-for="selector in selectors" v-on:click="selected=selector.value,selectorshow=!selectorshow" v-bind:key="selector.id">{{selector.text}}</div>
    </div>
    </transition>
<!-- カスタマイズ用のdivタグ -->

    <select name="" id="" v-model="selected">
      <option v-for="selector in selectors" v-bind:value="selector.value" v-bind:key="selector.id">{{selector.text}}</option>
    </select>
  </div>
</div>

CSS

*{
  box-sizing: border-box;
}
ul,li{
  margin: 0;
  padding: 0;
  list-style: none;
}

.selectBox{
position: relative;
width: 10em;
height: 60px;
}
.selectBox select{
  position: absolute;
  left: 100%;
  top: 100%;
  width: 100%;
  height: 100%;
}
.selectBox__output{
  display: flex;
  align-items: center;
  position: relative;
  width: 100%;
  height: 100%;
  padding: 1em;
  border: 1px solid #ccc;
  background-color: #fff;
  border-radius: 5px;
  z-index: 2;
}
.selectBox__output::after{
  display: block;
  position: absolute;
  right: 3%;
  top: 50%;
  font-family: "CONDENSEicon";
  transform: translateY(-50%);
  content: "û";
}
.selectBox__output.open::after{
  transform: translateY(-50%) rotate(180deg);
}
.selectBox__selector{
  position: absolute;
  left: 0;
  top: calc(100% - 1px);
  width: 100%;
  border: 1px solid #ccc;
  background-color: #fff;
  transform-origin: left top;
  z-index: 10;
}
.selectBox__selectorItem{
  width: 100%;
  padding: .75em;
}
.selectBox__selectorItem+.selectBox__selectorItem{
  border-top: 1px solid #ccc;
}
.selectBox__selectorItem:hover{
  background-color: #0d61ad;
  color:#fff;
}

.accodion-enter-active, .accodion-leave-active {
  transition: .5s;
  overflow: hidden;
}
.accodion-enter, .accodion-leave-to {
  transform: scaleY(0);
}
.accodion-leave, .accodion-enter-to {
  transform: scaleY(1);
}

Vue.js

var tab = new Vue({
  el: '#select',
  data: {
    selected: 'cat1',
    selectorshow: false,
    selectors: [
      { id: 1, text: "カテゴリ1", value: "cat1" },
      { id: 2, text: "カテゴリ2", value: "cat2" },
      { id: 3, text: "カテゴリ3", value: "cat3" },
    ]
  }
});

簡単な説明

まず、JSでselectorsという配列を用意し、optionタグと見た目カスタマイズ用のdivタグにv-forで展開します。

HTMLでは、selectタグにv-modelディレクティブにselectedという値を持たせています。これは、selectタグの変更を変数に格納するためのもので、カスタマイズ用のdivタグと値を共有できるようになります。
selectedには、カスタマイズ用のdivタグで選択した要素の値が格納されるようになっており、jQueryでやっていたJSで取得して格納して出力みたいなのをこれ一個でやってくれます。

カスタマイズ用のdivタグは、.selectBox__outputという出力用のタグと、.selectBox__selectorという選択肢用のタグがあります。
出力用のタグでは、クリック時に変数のbool値を操作しており、そのbool値によってクラス追加や選択肢用のタグの表示非表示を操作しています。v-bind:classに設定されてるのは三項演算子というもので、selectorshow変数がtrueならopenクラスをつけるというものです。

選択肢用のタグでは、v-showディレクティブでselectorshow変数がtrueなら表示するというようになっています。
それぞれの選択肢ではクリック時にselectedに配列のvalueの値を格納するようにしています。
このvalueの値はoptionタグのvalueの値と一緒になっているので、選択されたものがselectedに格納され、v-modelディレクティブを通じてselectタグに反映されるようになっています。
一緒にv-on:clickの値になっているのは、選択肢を閉じる為のものです。

また、選択肢を囲むようにtransitionというタグがあります。
これはv-showディレクティブで表示非表示を切り替えるときにアニメーションをつける為のもので、これで囲むとCSSでアニメーションを設定できます。
CSSの最後の10行がそれになります。

まとめ

v-modelという便利なやつのおかげで簡単にselectタグとの連動ができました。
これが双方向バインディングってやつです。
「双方向」なので、今回はやってませんが、selectタグの値を変更してもdivタグの方に反映されます。
selectタグ以外のform系要素で使えるそうなので、またいろいろやってみたいと思います。

このくらいになると、だいぶVue.jsの方が便利という感じがしてきました。
そろそろ規模が大きめのものにチャレンジしてみようかな。。。