Vuetifyを使った開発 3 ~ナビゲーションとAppバーのカスタマイズ~

IT

Vuetifyでは、ナビゲーション(多くのサイトでは左側のメニュー)や、Appバー(画面上部のヘッダー)がコンポーネントとして用意されており、簡単に作成することができます。また、これらコンポーネントはカスタマイズも容易です。

今回は、ナビゲーションとAppバーのカスタマイズについて、公式サイトを参考にしながら試した結果をまとめます。

前提条件

oswindows11
nodev20.11.1
npm10.5.0
Vue3.4.21
Vuetify3.5.9
エディターVS Code

ナビゲーションの設定

ナビゲーションの幅指定

一般的に幅の指定と言えば、CSSやHTMLの属性で「width」を指定します。しかし、Vuetifyでは別の方法で指定することができます。NavigationDrawerのAPIで「Props」を見てみると、「width」というプロパティが定義されていることが分かります。そのため、NavigationDrawerコンポーネントを呼び出す際に、widthプロパティに希望する幅を指定すると、幅を調整できます。

// 「v-bind:width」もしくは省略形で「:width」用いて指定する
<v-navigation-drawer :width="180" color="secondary" permanent>
</v-navigation-drawer>

width以外にも様々なプロパティが用意されています。カスタマイズしたいときにプロパティを活用できないか見てみるといいと思います。

ナビゲーションの開閉 パターン1

よくある左側のナビゲーションの開閉を実装してみたいと思います。イメージは以下の通りです。Appバー左上のハンバーガーボタンを押すことで切り替えるように設定します。

開閉制御自体はすごく簡単で、「v-navigation-drawer」に「v-model」を指定するだけです。v-modelに「false」が設定されるとナビゲーションが閉じます。詳細はこれから説明する「ナビゲーションコンポーネント」のコードをご覧ください。

ただし、今回はコンポーネントの親子関係を以下のように作成しています。そのため、子コンポーネントのイベントを別の子コンポーネントに伝える必要があり、実装がやや複雑になります。

上記の構成はあくまで1例です。Appバーとナビゲーションを同一コンポーネントにすればより簡単に実装できます。

それでは、実際のコードを見てきましょう。まずは、イベントを発生させる元のAppバーコンポーネントです。

以降で紹介するコードでは、ナビゲーションの開閉に関するものだけを記載しています。

7行目で「drawer-change」という名前のイベントを定義します。このイベントはboolean型の引数を1つ受け取ります。そして、20行目でハンバーガーボタンをクリックしたときに、on/off(true/false)を切り替えるためのクリックイベントを指定します。そして、クリックした際に「drawer-change」イベントを発行します。

// Appバーコンポーネント

<script setup lang="ts">
import { ref } from 'vue';

const isOpen = ref(true)
defineEmits<{
    (e: 'drawer-change', isOpen: boolean): void
}>()

const changeDrawer = () => {
    isOpen.value = !isOpen.value
    return isOpen.value
}
</script>

<template>
    <v-app-bar color="primary" density="compact">
        <template v-slot:prepend>
            <v-app-bar-nav-icon @click="$emit('drawer-change', changeDrawer())"></v-app-bar-nav-icon>
        </template>
        <v-app-bar-title>App bar</v-app-bar-title>
        <template v-slot:append>
            <v-btn icon="mdi-dots-vertical"></v-btn>
        </template>
    </v-app-bar>
</template>

続いて、Appバーコンポーネントを呼び出す親コンポーネントのコードを見ていきましょう。ここでは、14行目でAppバーコンポーネントを呼び出す際に、「drawer-change」イベントと、イベントが発行されたときに実行される関数を指定します。このように設定することで、Appバーコンポーネントのハンバーガーボタンでture/falseを切り替えるたびに、6行目で定義している「isOpen」の値もtrue/falseが切り替わります

そして、15行目でナビゲーションコンポーネントを呼び出す際に、この「isOpen」をプロパティとして渡します。これで、ナビゲーションコンポーネントにAppバーコンポーネントの変更イベントが伝わります。

// 親コンポーネント

<script setup lang="ts">
import { ref } from 'vue';

const isOpen = ref(true)
const updateDrawerStatus = (status: boolean) => {
  isOpen.value = status
}
</script>

<template>
  <v-app>
    <MyBar @drawer-change="updateDrawerStatus" />
    <MyNavigation :drawer="isOpen" />
    <v-main>
      {{ isOpen }}
    </v-main>
  </v-app>
</template>

ナビゲーションバーコンポーネントでは、「drawer」というプロパティを用意して親コンポーネントからtrue/falseの値を受け取ります。そして、この値は最終的に10行目のv-modelに反映されます。その結果、trueであればナビゲーションが表示され、falseであれば非表示になります。

// ナビゲーションコンポーネント

<script setup lang="ts">
const props = defineProps({
    drawer: Boolean
})
</script>

<template>
    <v-navigation-drawer :width="180" v-model="props.drawer" color="secondary" permanent>
        <v-list-item title="Navigation" subtitle="title">{{ props.drawer }}</v-list-item>
        <v-divider></v-divider>
        <v-list-item link title="Home"></v-list-item>
        <v-list-item link title="Test"></v-list-item>
    </v-navigation-drawer>
</template>

ナビゲーションの開閉 パターン2

2つ目の方法では「rail」プロパティを使います。NavigationDrawerで使用可能なプロパティを再度確認すると、「rail」と呼ばれるプロパティがあることが分かります。「rail」が取り得る値はbooleanです。

ただし、こちらは1つ目の方法と動作がやや異なります。実際の動作を見てみましょう。「v-mode」で制御したときは、ナビゲーションが完全に消えていましたが、「rail」ではアイコンが残っていままとなります。

rail」を使用した際のナビゲーションコンポーネントのコードを見てみましょう。

14行目で、ナビゲーションをクリックするたびにrailの値を変更する設定をしています。「rail = true」の時に、ナビゲーションが縮小します。なお、今回は指定していませんが、縮小した際の幅は「rail-width」プロパティで指定できます。デフォルトは56となっています。

// ナビゲーションコンポーネント

<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps({
    drawer: Boolean
})

const rail = ref(false)
</script>

<template>
    <v-navigation-drawer :width="180" v-model="props.drawer" color="secondary" 
        :rail="rail" @click.stop="rail = !rail" permanent>
        <v-list-item title="Navigation" subtitle="title"></v-list-item>
        <v-divider></v-divider>
        <v-list-item prepend-icon="mdi-home" link title="Home"></v-list-item>
        <v-list-item prepend-icon="mdi-cog" link title="Test"></v-list-item>
    </v-navigation-drawer>
</template>

ナビゲーションメニューのグループ化

メニューをカテゴリごとに分類して、メニューのリストの表示・非表示を制御する設定を追加します。こちらも実際に見たほうが早いと思うので、まずは以下をご覧ください。

メニュー1つ1つは「v-list-item」で作成しています。まずはこれを「v-list」で囲みます(5行目参照)。v-listのプロパティとして、「open-strategy=”multiple”」を指定しています。multiple以外には「list」や「single」を指定できます。singleを指定すると、複数のグループのうちどれか1つしか開くことができなくなります。先ほどのgifでは、グループ1を開いた後にグループ2を開いても、グループ1は開いたままとなっています。しかし、singleを設定することで、グループ2を開いて瞬間にグループ1が閉じます。

続いて、グループ化を「v-list-group」を用いて作成します。そして、親メニューは「v-slot:activator」の中に、子メニューは「v-list-group」の直下に記載します。

<!-- ナビゲーションコンポーネント -->
    
  <v-navigation-drawer :width="200" v-model="props.drawer" color="secondary" permanent>
        <v-list-item title="Navigation"></v-list-item>
        <v-divider></v-divider>
        <v-list open-strategy="multiple">
            <v-list-group value="グループ1">
                <template v-slot:activator="{ props }">
                    <v-list-item v-bind="props" title="グループ1"></v-list-item>
                </template>
                <v-list-item prepend-icon="mdi-home" link title="Home"></v-list-item>
            </v-list-group>

            <v-list-group value="グループ2">
                <template v-slot:activator="{ props }">
                    <v-list-item v-bind="props" title="グループ2"></v-list-item>
                </template>
                <v-list-item prepend-icon="mdi-cog" link title="Test"></v-list-item>
            </v-list-group>
        </v-list>
    </v-navigation-drawer>

子メニューのように、v-list-group直下に直接記載した場合は、内部的にはdefaultスロットとしてv-list-groupコンポーネントに渡されています。つまり、以下のように「v-slot:default」を明示的に書くこともできます。

<v-list-group value="グループ1">
   <template v-slot:activator="{ props }">
      <v-list-item v-bind="props" title="グループ1"></v-list-item>
   </template>
   <template v-slot:default>
      <v-list-item prepend-icon="mdi-home" link title="Home"></v-list-item>
   </template>
</v-list-group>

Vuetifyでは、コンポーネントを使うだけでこんなに簡単にいろんなことができます。親メニューがクリックされたら、子メニューを表示するような処理を自分で頑張って書く必要はありません。

Appバーの設定

ボタンやアイコンの位置の設定

ナビゲーションの説明の際に映っていたAppバーでは、ハンバーガーメニューを右側に、三点リーダーを左側に配置しています。このような設定をCSSで一から書こうとすると難しいと思います。しかし、Vuetifyを使えば簡単に設定できます。

AppBarのAPIを見てみましょう。名前付きのslotが定義されていることが分かります。これを利用することで、Appバー内でのボタンなどの配置を制御します。

以下のように、Appバーは①(ハンバーガーボタンとトグルボタン)、②(タイトル)、③(三点リーダー)の3要素で構成されています。タイトルは「v-app-bar-title」という専用のコンポーネントに記載します。そして、タイトルより左側に配置したいコンポーネントは「v-slot:prepend」というスロットに、右側に配置したいコンポーネントは「v-slot:append」というコンポーネント内に記載します。

CSSで細かく位置を制御する必要はありません。slotを活用することで、すべていい感じに配置してくれます。

最後に

Vuetifyでは画面上部のバーや左側のナビゲーション(メニュー)など、一般的に使われることの多いデザインコンポーネントは基本的に用意されています。また、好みに合わせてカスタマイズも簡単に行えます。Appバーやナビゲーションでは、今回紹介したプロパティ以外にも、「location」を使用して位置を変えることもできます。

また、ナビゲーションの開閉を行う際に、CSSで条件ごとにwidthを設定したり、<template>内に条件文を書いたりする必要がありません。各コンポーネントで定義されているプロパティを利用することで簡単に設定できます。そのため。それらしいデザインを爆速で開発できる便利なものとなっています。

コメント