iPhoneで Grafanaの グラフを 参照できる アプリ Grafanizer 作ってます。 詳しくは こちらへ

Vue.jsでBulmaのレスポンシブ状態を判定する

Dec 13, 2017  
#vue.js #bulma #adventcalendar

はじめに

これは Vue.js #4 Advent Calendar 2017 用に作成したポストです。

最近新しいサービスを作るときは、 Vue.jsBulma を組み合わせて作っています。どちらも直感的に開発することができるので、かなり開発効率が高いです。

Vue.jsBulma は非常に相性が良く、互いに扱う領域が別れているため通常の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 の状態になると再計算する仕組みとしています。

あまり詳しく説明する必要はないと思いますが、おおまかな流れは以下のとおりです。

  1. Bulma の各クラスをつけて bulmaTypes 分のエレメントを作成
  2. 各エレメントは BulmaDisplay スタイルが制御されているので、 flex になっているエレメントを検出
  3. 検出結果を bulmaClasses に照合して bulma を作成

おわりに

それほど難しいことはやってませんが、なかなか便利なものが出来たと思います。

例えばモバイル向け表示の場合とデスクトップ向け表示でJSのアクションを変えるとか、モバイル向け表示のときだけ値を変えるとか使いみちはいろいろありそうです。

これで Bulma で判定したレスポンシブ具合を使って Vue.jsBulma 向けに :class="isMobile? 'isMobile': 'isHiddenMobile'" 等という Bulma 用のClass名を与えて、なにをやってるか分からないことも出来るようになりますね!


Vue.jsはJavascriptFramework、BulmaはCSSFrameworkと完全に分業できているのが使いやすさに対する大きなアドバンテージになっているのは確かなところ。

だけど物事はそう簡単にいかないので、お互いがお互いの状況を知れることによってもっと良いものになるのは間違いない。