先日、jQueryを使ってselectタグをカスタマイズする方法をブログに書いたんですが、JSが長いし、なんかややこしいし、ということでVue.jsで作り直してみました。
実装方法
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の方が便利という感じがしてきました。
そろそろ規模が大きめのものにチャレンジしてみようかな。。。