Vue + TypeScript + Babel + Bulma + webpackで作るSPAでレスポンシブなフロント開発4(CSS Framework編)


リンク

前回はJavaScript周りの説明をしました。

github

今回はCSS周りです。
使用するcss frameworkはbulmaというライブラリです。

特徴として

  • SASSでできている。
  • JQuery(JavaScript)の依存なし
  • レスポンシブ
  • FlexBoxでグリッドデザイン

のような特徴があります。

カラムとグリッドデザイン

まずはComponent側

SampleColumnComponent.ts
/// <reference path="../../index.d.ts"/>
import Vue from 'vue'
import Component from 'vue-class-component'
import HtmlTemplate from '../../../vhtml/sample-column.vhtml'

@HtmlTemplate
@Component({
})
export class SampleColumnComponent extends Vue {
    st = "RIGHT"

    get selectType(){
        return this.st;
    }

    set selectType(value) {
        this.st = value;
    }
}

SampleColumnComponent.tsはselectTypeのプロパティくらいです。

view側はモバイルサイズの時とタブレット以上の大きさの時でデザインを変えています。

sample-column.vhtml
<div>
    <!-- ラジオボタンはモバイルのサイズの時のみ表示 -->
    <div class="control is-hidden-tablet">
        <label class="radio">
            <input type="radio" id="lf" value="LEFT" v-model="selectType">
            <label for="lf">左側</label>
        </label>
        <label class="radio">
            <input type="radio" id="ri" value="RIGHT" v-model="selectType">
            <label for="ri">右側</label>
        </label>
    </div>
    <div class="columns">
        <!-- 左側 モバイルの時のみ表示切り替え発生-->
        <div class="column is-6-tablet" v-bind:class="{'is-hidden-mobile':selectType !== 'LEFT'}">
            <div class="box">
                <article class="media">
                    <div class="media-left">
                        <figure class="image is-64x64">
                            <img src="https://bulma.io/images/placeholders/128x128.png" alt="Image">
                        </figure>
                    </div>
                    <div class="media-content">
                        <div class="content">
                            <p>
                                左側
                            </p>
                        </div>
                    </div>
                </article>
            </div>
        </div>
        <!-- 右側 モバイルの時のみ表示切り替え発生 -->
        <div class="column is-6-tablet" v-bind:class="{'is-hidden-mobile':selectType !== 'RIGHT'}">
            <div class="field">
                <label class="label">右側</label>
                <div class="control">
                    <input class="input" type="text" placeholder="Text input">
                </div>
                <p class="help">This is a help text</p>
            </div>
            <div class="field is-grouped">
                <div class="control">
                    <button class="button is-link">Submit</button>
                </div>
                <div class="control">
                    <button class="button is-text">Cancel</button>
                </div>
            </div>
        </div>
    </div>
</div>

ここのポイントは3点

  • カラム
  • is-hidden-tabletとis-hidden-mobile
  • v-bind:class

カラムはcolumn is-6-tabletとかです。
グリッドデザインは全体で12分割で考えます。
is-6-tabletは画面が大きいときに半分の大きさで表示してね。ってことです。
小さい時は指定しないので12分(全部)使って表示してね。ってことです。

is-hidden-tabletとis-hidden-mobileを簡単に説明するとブラウザの表示しているサイズにより表示、非表示を制御しています。
is-hidden-tabletがついていると画面が大きいときに表示しない
is-hidden-mobileがついていると画面が小さいときに表示しない

v-bind:class="{'is-hidden-mobile':selectType !== 'LEFT'}"は動的にis-hidden-mobileをつけます。
selectTypeがLEFT以外のときにis-hidden-mobileをつけます。
is-hidden-mobileがついているってことは、画面が小さい時のみ動作します。
画面が大きい時はクラスがついてても関係ありません。

クラスについて、詳しくは本家Bulmaのサイトに詳しく載っています。

JavaScriptが必要な部分

BootstrapやFoundationはvue環境で使えなくはないとは思うのですが、JQuery依存なので、今回は見送りとしました。
JavaScriptがないということはちょっとした実装は自分でする必要があります。
ちょっとした実装の例としてモバイルメニューとモーダルの実装をしてみたいと思います。

モバイルメニュー

NavigationComponent.ts
/// <reference path="../../index.d.ts"/>
import Vue from 'vue'
import Component from 'vue-class-component'
import HtmlTemplate from '../../../vhtml/navigation.vhtml'
import router from '../router/mainRouter'

@HtmlTemplate
@Component({
    router,
})
export class NavigationComponent extends Vue {
    flag = false;

    get opendMenu() {
        return this.flag;
    }

    clickByToggleMenuButton() {
        this.flag = !this.flag;
    }
}
navigation.vhtml
<nav class="navbar is-transparent is-black">
    <div class="navbar-brand">
        <router-link :to="{name:'top'}" class="navbar-item">
            top
        </router-link>
        <!-- メニューが小さい時にバーガーボタンを表示 -->
        <div class="navbar-burger burger" v-bind:class='{"is-active":opendMenu}' data-target="navbarExampleTransparentExample" @click="clickByToggleMenuButton">
            <span></span>
            <span></span>
            <span></span>
        </div>
    </div>

    <div id="navbarExampleTransparentExample" class="navbar-menu" v-bind:class='{"is-active":opendMenu}'>
        <div class="navbar-start">
            <!-- 存在しないリンク先 -->
            <router-link to="/aaaaaaa" class="navbar-item">page not found</router-link>
            <div class="navbar-item has-dropdown is-hoverable">
                <div class="navbar-link">event</div>
                <div class="navbar-dropdown is-boxed">
                    <router-link :to="{name:'sample-button'}" class="navbar-item">button</router-link>
                    <router-link :to="{name:'sample-bind'}" class="navbar-item">bind</router-link>
                    <router-link :to="{name:'sample-load'}" class="navbar-item">load</router-link>
                </div>
            </div>
            <div class="navbar-item has-dropdown is-hoverable">
                <router-link to="/sample-parameter" class="navbar-link">parameter</router-link>
                <div class="navbar-dropdown is-boxed">
                    <router-link :to="{name:'sample-parameter', params: { id: 123 }}" class="navbar-item">/123</router-link>
                    <router-link :to="{name:'sample-parameter', params: { id: 'aaa' }, query: { test: 'bbb' }}" class="navbar-item">/aaa?test=bbb</router-link>
                </div>
            </div>
            <div class="navbar-item has-dropdown is-hoverable">
                <div class="navbar-link">css framework</div>
                <div class="navbar-dropdown is-boxed">
                    <router-link :to="{name:'sample-modal-button'}" class="navbar-item">modal button</router-link>
                    <router-link :to="{name:'sample-column'}" class="navbar-item">column</router-link>
                </div>
            </div>
        </div>

        <div class="navbar-end">
            <div class="navbar-item">
                <div class="field is-grouped">
                    <p class="control">
                        <a class="bd-tw-button button" data-social-network="Twitter" data-social-action="tweet" data-social-target="http://localhost:4000" target="_blank" href="https://twitter.com/intent/tweet?text=Bulma: a modern CSS framework based on Flexbox&amp;hashtags=bulmaio&amp;url=http://localhost:4000&amp;via=jgthms">
                            <span class="icon">
                                <i class="fab fa-twitter"></i>
                            </span>
                            <span>Tweet</span>
                        </a>
                    </p>
                    <p class="control">
                        <a class="button is-primary" href="https://github.com/jgthms/bulma/archive/0.5.1.zip">
                            <span class="icon">
                                <i class="fas fa-download"></i>
                            </span>
                            <span>Download</span>
                        </a>
                    </p>
                </div>
            </div>
        </div>
    </div>
</nav>

簡単に説明するとクリックイベントを押下時にフラグを逆にしています。
v-bind:class='{"is-active":opendMenu}'

navbar-burgerはモバイル時にのみ表示されます。
navbar-burgerにis-activeをつけると切り替えられます。

くわしくはBulma Navbarをみてください。

モーダル

モーダルもCSSのみでは動作できなくて、JavaScriptを必要とします。

SampleModalModule.ts
/// <reference path="../../index.d.ts"/>

import {ActionTree, GetterTree, ModuleTree, MutationTree} from "vuex";
import {MainState} from "../store/MainStore";

const NAMESPACE = 'sample-modal';
const NAMESPACE_ACTION = `${NAMESPACE}/a/`
const NAMESPACE_GETTER = `${NAMESPACE}/g/`
const NAMESPACE_MUTATION = `${NAMESPACE}/m/`

export const ActionKey = {
}

export const GetterKey = {
}

export const MutationKey = {
    SET_SHOWN: `${NAMESPACE_MUTATION}SET_SHOWN`,
}

function createStore(){
    class State {
        constructor(
            public shown: Boolean = false,
        ) { }
    }

    const getters = {
    } as GetterTree<State, MainState>

    const actions = {
    } as ActionTree<State, MainState>

    const mutations = {
        [MutationKey.SET_SHOWN] (state, value) {
            state.shown = value;
        },
    } as MutationTree<State>

    return {
        state: new State(),
        getters,
        actions,
        mutations
    } as ModuleTree<State>
}

export default createStore()
SampleModalButtonComponent.ts
/// <reference path="../../index.d.ts"/>
import Vue from 'vue'
import Component from 'vue-class-component'
import HtmlTemplate from '../../../vhtml/sample-modal-button.vhtml'
import {SampleModalComponent} from "./SampleModalComponent";
import {MutationKey} from "../module/SampleModalModule";

@HtmlTemplate
@Component({
    components: { //ここでコンポーネントを読み込みます。
        "sample-modal":SampleModalComponent,
    },
})
export class SampleModalButtonComponent extends Vue {
    //クリックするとshownをtrueにして、モーダルを表示します。
    clickByShowModalButton() {
        this.$store.commit(MutationKey.SET_SHOWN, true)
    }
}
SampleModalComponent.ts
/// <reference path="../../index.d.ts"/>
import Vue from 'vue'
import Component from 'vue-class-component'
import HtmlTemplate from '../../../vhtml/sample-modal.vhtml'
import {MutationKey} from "../module/SampleModalModule";

@HtmlTemplate
@Component({
})
export class SampleModalComponent extends Vue {
    get shown() {
        return this.$store.state.sampleModal.shown
    }

    //クローズボタン押下時にshownをfalseにしてモーダルを非表示にします。
    clickByCloseButton() {
        this.$store.commit(MutationKey.SET_SHOWN, false)
    }

    clickByCancelButton() {
        this.$store.commit(MutationKey.SET_SHOWN, false)
    }
}
sample-modal.vhtml
<div class="modal" v-bind:class="{'is-active':shown}">
    <div class="modal-background"></div>
    <div class="modal-card">
        <header class="modal-card-head">
            <p class="modal-card-title">Modal title</p>
            <button class="delete" aria-label="close" @click="clickByCloseButton"></button>
        </header>
        <section class="modal-card-body">
            Content
        </section>
        <footer class="modal-card-foot">
            <button class="button is-success">Save changes</button>
            <button class="button" @click="clickByCancelButton">Cancel</button>
        </footer>
    </div>
</div>

is-activeがあるときのみ表示されます。
このis-activeの制御が自作JavaScriptが必要な部分です。

詳しくはBulma Modalを参考にして下さい。

github