Vuetify を使って困ったところ小ネタ集
Vue CLI + TypeScript + SCSS + Vue-Router + Vuex なプロジェクトに、$ vue add vuetify
で Vuetify を追加した。
Vuetify はマテリアルデザインを実現するフレームワークだが、細かなところでデザインを調整したい時に上手いやり方が分からないことがあり、ちょくちょく調べている。
今回はそんな、「ちょっとググッて調整した Vuetify 周りの小ネタ」を紹介する。
目次
- Overlays 表示時に縦スクロールバーが消えて画面幅がズレるのがウザい
scrollable
なv-dialog
の縦スクロールバーが見えたり消えたりするのがウザいv-list-item
で省略せず折り返し表示させたい- ページヘッダのタイトルにリンクを貼る
v-text-field
にrequired
属性だけ書いても必須項目にはならないv-list
の間に罫線を引く- テーマカラーを自分で決める
- 以上
Overlays 表示時に縦スクロールバーが消えて画面幅がズレるのがウザい
Dialog (v-dialog
) や Navigation Drawer (v-navigation-drawer
) などの Overlays を表示する時に、html
要素の縦スクロールバーが消えて、画面幅が若干広がって画面全体が左右にズレるのがキモい。html
要素に独自の CSS クラスが振られて、overflow-y
が制御されているのが原因だ。
次のようにページ全体のデザインに指定を入れれば回避できるが、オーバーレイの裏にある画面全体をスクロールできてしまうので、一長一短。
src/App.vue
// ↓ scoped 属性を書かないことでグローバルに適用させる
<style lang="scss">
html,
html.overflow-y-hidden {
overflow-y: scroll !important;
}
そうそう、views/
や components/
でスタイルを書く時に、
<style lang="scss" scoped>
と scoped
属性を付けることで、スタイルのスコープ化 (カプセル化) が出来るようになる。地味に知らなかった。
scrollable
な v-dialog
の縦スクロールバーが見えたり消えたりするのがウザい
先程のページ全体の縦スクロールバーと同様の話。
<!-- scrollable を付与することで、高さが出来た時に `v-card-text` に縦スクロールバーが付く -->
<v-dialog v-model="isShowDialog" persistent scrollable>
<v-card>
<v-card-title class="grey lighten-2">タイトル</v-card-title>
<v-divider></v-divider>
<v-card-text>
ココに高さが発生する可変な内容…
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn color="primary" v-on:click="isShowDialog = false">閉じる</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
この場合も、v-card-text
に縦スクロールバーが付いたり付かなかったりして、Dialog 内の幅がズレるので、次のように直す。
<v-dialog v-model="isShowDialog" persistent scrollable>
<v-card>
<!-- ↓ 次のように CSS クラスを振る -->
<v-card-text class="always-show-scrollbar">
ココに高さが発生する可変な内容…
</v-card-text>
</v-card>
</v-dialog>
.always-show-scrollbar {
overflow-y: scroll !important;
}
最初からスクロールバーは出しときゃいいじゃん派。
v-list-item
で省略せず折り返し表示させたい
リストを構築する v-list-item
は、中のテキストが長いと省略表示されてしまう。コレを折り返して全て表示させたい場合は、次のような CSS クラスを割り当てると良い。
<v-list-item-content>
<v-list-item-title>タイトル</v-list-item-title>
<v-list-item-subtitle class="wrap-text"> <!-- ← クラスを振っている -->
長いテキスト…
</v-list-item-subtitle>
</v-list-item-content>
v-list-item
に white-space: nowrap
指定があるので、次のような CSS クラスで打ち消してやる。
.wrap-text {
word-break: break-all;
white-space: normal;
}
ページヘッダのタイトルにリンクを貼る
ページのヘッダを構成する v-app-bar
。その中に配置した v-toolbar-title
が、画面全体のタイトルを表現する形になる。
せっかくならこの v-toolbar-title
にリンクを振りたいのだが、v-bind:to
とかが出来ない。
<v-app id="app">
<v-app-bar app color="primary" dark>
<!-- ↓ 以下の v-bind:to 属性は動かない -->
<v-toolbar-title v-bind:to="'/'">
タイトル
</v-toolbar-title>
</v-app-bar>
<v-main>
<v-container fluid>
<!-- カードやリストは v-bind:to が書けるのに… -->
<v-card v-bind:to="'/hoge'">
カード
</v-card>
<v-list dense>
<v-list-item link v-bind:to="'/home'">
<v-list-item-action>
<v-icon>mdi-home</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Home</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-container>
</v-main>
</v-app>
で、仕方がないので、Vue-Router の router-link
を中に入れてやることにした。このままだとリンクっぽい見た目になってしまうので、セットでテキストの色味も調整する。
<v-app id="app">
<v-app-bar app color="primary" dark>
<v-toolbar-title>
<!-- ↓ 素直に router-link を書く -->
<router-link to="/" class="toolbar-title">
タイトル
</router-link>
</v-toolbar-title>
</v-app-bar>
</v-app>
#app {
// v-toolbar-title 内のリンク色を直す
.toolbar-title {
color: inherit;
text-decoration: none;
}
}
コレでよき。
v-text-field
に required
属性だけ書いても必須項目にはならない
フォーム内のテキストボックスを必須入力にさせたく、次のように書いてみた。
<v-form v-model="form.isValid">
<v-text-field v-model="form.name" required label="名前"/>
<v-btn v-bind:disabled="!form.isValid" v-on:click="onSubmit" color="primary">送信する</v-btn>
</v-form>
v-form
の v-model
が Boolean になっていて、内部のフォーム部品たちのバリデーションが全部 OK になると true
になるという素敵な仕組み。コレを使って Submit するボタンの disabled
属性を変えてやれば、「送信」ボタン押下時に改めてバリデーションチェックを実装する必要がない。
v-text-field
には required
属性を振ったし、コレで OK か?と思ったら、どうも上手くバリデーションされていない様子。
調べてみると、コレは HTML5 の required
属性を渡しているに過ぎず、v-form
の v-model
で対応するバリデーションチェックには引っかからないようだ。
というワケで、やはり v-bind:rules
属性は必須というワケだった。なんなら required
属性の方は効果がないから消してしまっても良いくらい。
<v-form v-model="isValidForm">
<!-- v-bind:rules は配列を渡すので、[] 内に required 関数を入れている -->
<v-text-field v-model="name" required v-bind:rules="[required]" label="名前"/>
<v-btn v-bind:disabled="!isValidForm" v-on:click="onSubmit" color="primary">送信する</v-btn>
</v-form>
export default {
name: 'MyComponent',
// data は次の書き方をするとインデントが少なく済む (`data() { return {}; }` とするとインデントが一段増える)
data: () => ({
isValidForm: false,
name: ''
}),
methods: {
// 自分でバリデーション関数を用意する
required: (value) => `${value || ''}`.trim() !== '' || '必須入力です。',
onSubmit() { /* Submit 処理 */ }
}
}
バリデーションルールは data
側に持たせる例もある。
<v-text-field v-model="name" required v-bind:rules="nameRules" label="名前"/>
export default {
name: 'MyComponent',
data: () => ({
isValidForm: false,
name: '',
nameRules: [
(value) => `${value || ''}`.trim() !== '' || '名前は必須入力です。',
// ココに同様に関数を追加していくことで、複数のバリデーションを実現できる
]
})
}
この柔軟性、一度覚えると書きやすいが、書き方を忘れたり、他人のコードを読解する時になると辛くなってくる。
v-list
の間に罫線を引く
v-list
の行間に罫線を引く場合。1行目の上に余分に罫線が出たり、最終行の下に余計な罫線が出来たりするのを回避するため、ちょっとした調整が必要だった。
<v-card>
<v-list dense subheader>
<!-- template 要素自体はレンダリングされない -->
<template v-for="(myItem, index) in myItems">
<!-- 1行目以外 (2行目以降) に上線を引く -->
<v-divider v-bind:key="index" v-if="index >= 1"/>
<v-list-item v-bind:key="myItem.id">
<v-list-item-content>
<v-list-item-title>{{ myItem.name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-list>
</v-card>
罫線は v-divider
で引けば良いのだが、イイカンジに表示するために index
値を見て v-if
で表示要否を決めている。
このように複数要素を v-for
で回して表示させたりしたい時に、template
要素が使える。template
要素自体は最終的にレンダリングされないので、CSS の適用順も変わらず、単純に v-for
や v-if
などの制御文を逃して書けるので見通しやすくなる。
テーマカラーを自分で決める
テーマカラーを自分で決める際は、src/plugins/vuetify.ts
に書き込めば良い。Theme Generator というサイトで、カラーコードもしくは色変数名で出力できるので、使ってみると良いだろう。
指定の仕方はこんな感じ。ダークテーマの時も同じ色味が使われるようにしておくことで、部分的に dark
指定を入れている時も色味がおかしくならない。
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
// ↓ 他の文献では違うパスを見かけたが、vuetify@2.3.2 ではコレを import するのが正しいみたい
import colors from 'vuetify/lib/util/colors';
Vue.use(Vuetify);
// https://theme-generator.vuetifyjs.com/ をベースに作った
const customTheme = {
primary : colors.pink.base, // #e91e63
secondary: colors.teal.base, // #009688
accent : colors.cyan.base, // #00bcd4
error : colors.red.base, // #f44336
warning : colors.amber.base, // #ffc107
info : colors.blue.base, // #2196f3
success : colors.green.base // #4caf50
};
export default new Vuetify({
// ↓ 以下のように書く
theme: {
themes: {
light: customTheme,
dark : customTheme
}
}
});
あとは以下の要領でテーマ名を指定してやれば良い。
<v-btn color="primary">通常ボタン</v-btn>
<v-btn color="warning">警告ボタン</v-btn>
<p class="warning--text">警告文</p>
<p class="error--text">エラー文</p>
Vuetify は文字色指定だけ 【色名】--text
と逆転して書く形になるのが違和感。w
以上
何やらちょこちょこ調べながら書くのが大変ではあるが、さすがは Material Design。見栄えは良いな…。