はじめに
これは Vue.js #4 Advent Calendar 2017 用に作成したポストです。
最近新しいサービスを作るときは、 Vue.js と Bulma を組み合わせて作っています。どちらも直感的に開発することができるので、かなり開発効率が高いです。
Vue.js と Bulma は非常に相性が良く、互いに扱う領域が別れているため通常のHTMLであれば何も考えることなく利用できます。
しかし、現在主流なレスポンシブ対応ページではブレークポイント毎に違ったコンテンツになる場合が少なくありません。場合によってはHTMLがかなり冗長になってしまいます。
そんなとき、例えばJS側でブラウザのウィンドウサイズを検出して処理を分けるということも考えられますが、今回は表示状態を検出して Bulma で利用できるClass名と同等なプロパティを Vue.js に用意してみます。
と言っても、大層なことをしてるわけではないので、まずはコード全体を紹介し、次に内容の説明をしようと思います。多分コードを見ただけでも意味がわかるとは思いますが。
コード
<div id="app">
<div v-if="bulma.isMobile">isMobile</div>
<div v-if="bulma.isTablet">isTablet</div>
<div v-if="bulma.isDesktop">isDesktop</div>
<div v-if="bulma.isWidescreen">isWidescreen</div>
<div v-if="bulma.isFullHD">isFullHD</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
bulma: {},
bulmaTypes: ["isMobile", "isTablet", "isDesktop", "isWidescreen", "isFullHD"],
bulmaClasses: {
isFlexTouch: [1, 1, 0, 0, 0],
isFlexTablet: [0, 1, 1, 1, 1],
isFlexDesktop: [0, 0, 1, 1, 1],
isFlexWidescreen: [0, 0, 0, 1, 1],
isFlexFullHD: [0, 0, 0, 0, 1],
isHiddenMobile: [0, 1, 1, 1, 1],
isHiddenTabletOnly: [1, 0, 1, 1, 1],
isHiddenDesktopOly: [1, 1, 0, 1, 1],
isHiddenWidescreenOnly: [1, 1, 1, 0, 1],
isHiddenTouch: [0, 0, 1, 1, 1],
isHiddenTablet: [1, 0, 0, 0, 0],
isHiddenDesktop: [1, 1, 0, 0, 0],
isHiddenWidescreen: [1, 1, 1, 0, 0],
isHiddenFullHD: [1, 1, 1, 1, 0]
}
},
mounted() {
this.bulma = null;
window.addEventListener("resize", () => {
this.bulma = null;
})
},
watch: {
bulma() {
if (this.bulma != null) { return; }
let ret = {};
const suffix = "Bulma";
let isFullHD = true;
for (const type of this.bulmaTypes) {
const div = document.createElement("div");
const klass = type.replace(/([A-Z])/, (m) => { return "-flex-" + m.toLowerCase(); });
div.setAttribute("id", type + suffix);
div.setAttribute("class", klass);
this.$el.appendChild(div);
const el = document.querySelector("#" + type + suffix);
const style = window.getComputedStyle(div, null);
ret[type] = false;
if (style.display == "flex") {
ret[type] = true;
isFullHD = false;
}
el.remove();
}
ret["isFullHD"] = isFullHD;
const size = this.bulmaTypes.indexOf(Object.keys(ret).filter((d) => { return ret[d] == true; })[0]);
for (const klass in this.bulmaClasses) {
ret[klass] = (this.bulmaClasses[klass][size] == 1) ? true : false;
}
this.bulma = ret;
}
}
})
</script>
解説
HTML
<div id="app">
<div v-if="bulma.isMobile">isMobile</div>
<div v-if="bulma.isTablet">isTablet</div>
<div v-if="bulma.isDesktop">isDesktop</div>
<div v-if="bulma.isWidescreen">isWidescreen</div>
<div v-if="bulma.isFullHD">isFullHD</div>
</div>
ここは検証用のHTMLを記載しています。 Bulma で検出したブレークポイントの情報が表示されるはずです。
Vue.data
data
の各変数は、
bulma
: Vue.js から実際に利用できるClassのプロパティをセットbulmaTypes
: 現時点で Bulma に定義されているブレークポイントbulmaClasses
: ブレークポイントを組み合わせテーブル
ブレークポイントは現在5つありますが、以前は FullHD
がありませんでした。今後も増える可能性があるので、その場合は調整が必要です。
先程のHTML項にあるように、 Vue.js からレスポンシブ具合を判定する場合は bulma.isXXXXX
のように利用します。
Vue.mounted
ここで初期化とリサイズ時の再計算を実行します。実際のコードは次項の watch
になります。
Vue.watch
ここが実際の判定コードになり、bulma = null
の状態になると再計算する仕組みとしています。
あまり詳しく説明する必要はないと思いますが、おおまかな流れは以下のとおりです。
- Bulma の各クラスをつけて
bulmaTypes
分のエレメントを作成 - 各エレメントは Bulma で
Display
スタイルが制御されているので、flex
になっているエレメントを検出 - 検出結果を
bulmaClasses
に照合してbulma
を作成
おわりに
それほど難しいことはやってませんが、なかなか便利なものが出来たと思います。
例えばモバイル向け表示の場合とデスクトップ向け表示でJSのアクションを変えるとか、モバイル向け表示のときだけ値を変えるとか使いみちはいろいろありそうです。
これで Bulma で判定したレスポンシブ具合を使って Vue.js で Bulma 向けに :class="isMobile? 'isMobile': 'isHiddenMobile'"
等という Bulma 用のClass名を与えて、なにをやってるか分からないことも出来るようになりますね!
Vue.jsはJavascriptFramework、BulmaはCSSFrameworkと完全に分業できているのが使いやすさに対する大きなアドバンテージになっているのは確かなところ。
だけど物事はそう簡単にいかないので、お互いがお互いの状況を知れることによってもっと良いものになるのは間違いない。