Nuxt3 × Vuetify3でQiita風のヘッダーを作成してみた

## はじめに
Nuxt3とVuetify3を使用して、Qiita風のヘッダーを作成してみました。
Nuxt3とVuetify3でWebサイトやWebアプリケーションの作成を始めたい方におすすめです。

## 開発環境
- Windows 11
- Nuxt.js 3.8.0
- Vuetify 3.4.0-alpha.1
- npm 8.19.4
- Node.js 16.20.0

## ファイル構成
今回のプロジェクトの最終的なファイル構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/a768e5cf-7945-9acb-4620-a02d3fb06a40.png)

## 完成イメージ
完全に再現はできていませんが、Qiitaのヘッダーに似たデザインにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/dc8b7d60-b3ec-9576-f4c3-a06849e4b6f4.png)


## 実装
### Nuxt3プロジェクトの作成

1. Nuxt3プロジェクトを作成します。

```
npx nuxi@latest init nuxt3-app-test
```
　　
2. 作成したNuxt3プロジェクトのディレクトリに移動します。
```
cd nuxt3-app-test
```
　　
3. 依存関係をインストールします。
```
npm install
```
　　
4. 試しに開発モードで起動します。
```
npm run dev -- -o
```
　　
以下のような画面が表示されれば、Nuxt3プロジェクトの作成完了です。  
![localhost_3000_ (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/cdc3ef00-c895-cc55-6165-fabda3455272.png)

### Vuetify3の導入
次に、作成したNuxt3プロジェクトにVuetify3を導入します。
　　
1. 以下のコマンドで、Vuetify3の最新版と、スタイルを適用するのに別途必要なmdiとSASSを開発用の依存関係としてインストールします。
```
npm install vuetify@next mdi sass --save-dev
```
　　
2. pluginsディレクトリをルートディレクトリ直下に作成し、その中にvuetify.tsファイルを作成します。vuetify.tsは以下のようにします。
```javascript:vuetify.ts
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

export default defineNuxtPlugin((nuxtApp) => {
    const vuetify = createVuetify({
        components,
        directives,
    });

    nuxtApp.vueApp.use(vuetify);
});
```
上記のコードではVuetifyを使用するために必要なモジュールをインストールし、defineNuxtPluginの中でVuetifyのインスタンスを作成してマウントしています。これにより、Vuetifyのコンポーネントやディレクティブが利用できます。
　　
3. nuxt.config.tsを以下のように書き換えます。
```javascript:nuxt.config.ts
export default defineNuxtConfig({
    typescript: {
        shim: false,
    },
    ssr: false,
    css: ["vuetify/lib/styles/main.sass", "mdi/css/materialdesignicons.min.css"],
    build: {
        transpile: ["vuetify"],
    },
    vite: {
        define: {
            "process.env.DEBUG": false,
        },
    },
});
```
上記の「css:」部分でVuetifyのスタイルを指定し、「build:」部分でビルドプロセスにVuetifyが含まれるように指定しています。


### 背景色の変更
ヘッダーとの区別をつけるため、まずは背景色を変更します。
Vuetify3のデフィルトの背景色は「#ffffff（白）」ですが、Qiitaの背景色は「#f5f6f6」となっています。vuetify.tsで背景色を変更します。

1. vuetify.tsに以下を追加します。
```javascript:vuetify.ts
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

import type { ThemeDefinition } from "vuetify";　//追加

//Lightthemeを追加
const Lighttheme: ThemeDefinition = {
    dark: false,
    colors: {
        background: '#f5f6f6', //背景色の設定
    },
};

export default defineNuxtPlugin((nuxtApp) => {
    const vuetify = createVuetify({
        components,
        directives,

        //themeの追加
        theme: {
            themes: {
                light: Lighttheme,
                variables: {},
            },
        },
    });

    nuxtApp.vueApp.use(vuetify);
});
```
ThemeDefinitionというテーマのスタイルを定義する際に使用するインターフェースをインポートし、それをLightthemeとして定義しています。そして、colors内で背景色を指定し、createVuetify関数内でこのテーマを追加しています。
　　
2. 現状ではNuxt3の初期ページが表示されてしまうので、一旦app.vueを以下のようにします。
```javascript:app.vue
<template>
  <v-app>
  </v-app>
</template>
```  

　　
3. `npm run dev -- -o`を実行します。
以下のような画面が表示され、背景色が適用されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/f7998f63-2165-2e84-49a3-b0e14a47057c.png)

### ヘッダーの作成
ここからはヘッダーを作成していきます。
#### ヘッダーの追加
ヘッダーを追加します。
app.vueを以下のように書き換えます。
```javascript:app.vue
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
v-app-barでヘッダーを追加することができます。
高さは80pxにしています。
`elevation="0"`を指定することで、Qiitaと同じくフラットなデザインにします。elevationは立体感を調整するプロパティで、数字が大きくなるほど立体的なデザインになります。
`class="px-4 pt-1"`で上下左右に余白を少しつけています。
v-mainタグで、ヘッダーの下にコンテンツを表示することができます。
NuxtPageタグで、後ほど必要となるページ遷移を実装することができるようになります。　　
　　
コードを書き換えると、以下のような画面が表示され、白色でv-app-barが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/f04bebab-e8aa-81dc-a068-def524270217.png)


#### タイトルボタンの追加
タイトルボタンを追加します。
app.vueに`<template v-slot:prepend>`タグを追加し、中身を以下のようにします。
```javascript:app.vue
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
    //以下に追加
      <template v-slot:prepend>
        <v-btn color="#55c500" variant="flat">
          <p class="text-white font-weight-bold">
            タイトル
          </p>
        </v-btn>
      </template>
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
`v-slot:prepend`を使用することで、v-app-barの左端にv-btnを追加することができます。
v-btnのデザインは、なるべくQiita風になるようにスタイルを整えています。

コードを追加すると、以下のような画面が表示され、タイトルボタンが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/7f02604d-197f-955a-0541-3472966eb4a0.png)

#### 検索バーの追加
検索バーを追加します。
  
app.vueに`<template v-slot:append>`タグを追加し、中身を以下のようにします。
```javascript:app.vue
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
      <template v-slot:prepend>
        //省略
      </template>

    //以下に追加
      <template v-slot:append>
          <v-text-field :style="{ width: '280px' }" prepend-inner-icon="mdi-magnify" label="ページ内を検索" hide-details density="compact"/>
      </template>
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
`v-slot:append`を使用することで、v-app-barの右端にv-text-fieldを追加することができます。
幅は280pxに指定しています。
`prepend-inner-icon="mdi-magnify"`でテキストフィールドの左端に検索マークのアイコンを追加することができます。
`hide-details`はテキストフィールドの下に表示される注意書きなどを非表示にします。
`density="compact"`でテキストフィールドの高さをデフォルトよりも小さめにします。

コードを追加すると、以下のような画面が表示され、検索バーが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/76b00661-2cd2-e605-1406-415f87362b71.png)

#### 通知アイコン、アバターアイコンの追加
次に、通知アイコンとアバターアイコンを追加します。
app.vueにv-btnとv-avatarタグを追加し、中身を以下のようにします。
```javascript:app.vue
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
      <template v-slot:prepend>
        //省略
      </template>
      <template v-slot:append>
          <v-text-field :style="{ width: '280px' }" prepend-inner-icon="mdi-magnify" label="ページ内を検索" hide-details density="compact"/>
        //以下に追加
          <v-btn icon class="ml-2"><v-icon>mdi-bell</v-icon></v-btn>
          <v-avatar color="#55c500"></v-avatar>
      </template>
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
通知マークはmdiアイコンのbellで表示できます。
v-avatarで円形のユーザープロフィール画像を表示することができます。

コードを追加すると、以下のような画面が表示され、通知アイコンとアバターアイコンが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/7c9f1140-4fa7-4716-c571-3f9cf1610f45.png)

#### アバターアイコンをクリックして表示されるメニューの実装
次にアバターアイコンをクリックして表示されるメニューを実装していきます。メニュー内のボタンをクリックするとページ遷移します。

1. pagesディレクトリを作成し、その中にindex.vue、project.vue、account.vue、support.vueの4つのファイルを追加し、以下のようにしてください。
```html:index.vue
<template>
    <v-container>
        <h1>index.vueの中身です。</h1>
    </v-container>
</template>
```
```html:project.vue
<template>
    <v-container>
        <h1>project.vueの中身です。</h1>
    </v-container>
</template>
```

```html:account.vue
<template>
    <v-container>
        <h1>account.vueの中身です。</h1>
    </v-container>
</template>
```

```html:support.vue
<template>
    <v-container>
        <h1>support.vueの中身です。</h1>
    </v-container>
</template>
```
自分自身のファイル名を表示するシンプルなコードです。
　　
2. app.vueに以下のscriptタグとv-menuタグの中身を追加してください。
```javascript:app.vue
//scriptタグの追加
<script setup>
  const items = [
  { text: 'プロジェクト', icon: 'mdi-folder', path: '/project' },
  { text: 'アカウント', icon: 'mdi-account', path: '/account' },
  { text: 'お問い合わせ', icon: 'mdi-email', path: '/support' },
  ];
</script>
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
      <template v-slot:prepend>
        //省略
      </template>
      <template v-slot:append>
          <v-text-field :style="{ width: '280px' }" prepend-inner-icon="mdi-magnify" label="ページ内を検索" hide-details density="compact"/>
          <v-btn icon class="ml-2"><v-icon>mdi-bell</v-icon></v-btn>
        //v-menuタグの追加
          <v-menu offset-y>
            <template v-slot:activator="{ props }">
                <v-avatar v-bind="props" color="#55c500"></v-avatar>
            </template>
            <v-card class="mx-auto" width="220">
              <v-layout>
                <v-navigation-drawer>
                  <v-list>
                    <v-list-item>
                      <template v-slot:prepend>
                        <v-avatar color="#55c500"></v-avatar>
                      </template>
                      <v-list-item-title class="text-caption">ユーザーID</v-list-item-title>
                    </v-list-item>
                  </v-list>
                  <v-divider></v-divider>
                  <v-list :lines="false" density="compact" nav>
                    <v-list-item v-for="(item, i) in items" :key="i" :value="item" :to="item.path" color="primary">
                      <template v-slot:prepend>
                          <v-icon :icon="item.icon"/>
                      </template>
                      <v-list-item-title v-text="item.text"/>
                    </v-list-item>
                  </v-list>
                </v-navigation-drawer>
                <v-main style="height: 200px;"></v-main>
              </v-layout>
            </v-card>
          </v-menu>
      </template>
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
scriptタグ内では、メニュー内のボタンに表示されるテキスト、アイコン、遷移先のパスを指定しています。Nuxt3では作成したファイルの名前に応じて自動的にパスが生成されます。
  
v-menuタグの説明は以下の通りです。
- `offset-y`: メニューの表示位置をY軸方向（上か下か）に指定。
- `<template v-slot:activator="{ props }">`: v-menuコンポーネントのアクティベータスロット（メニューを表示するためのトリガーとなる要素）を定義。この場合、アクティベータとして `<v-avatar>` コンポーネントが使用されている。
- `<v-card>`: メニューの内容を囲む。width="220" でカードの幅を指定。
- `<v-navigation-drawer>` : Vuetifyのナビゲーションを描画するコンポーネントで、サイドバーとしてメニュー項目を表示するために使用。
- `<v-list>` : ナビゲーションドロワー内に表示されるリスト項目を表示。
    - `<v-list-item>`: リスト内の各項目
    - `<v-list-item-title> `: 項目のタイトルを表示。
    - `<v-divider>` : リスト項目の間に区切り線を表示。
- `<v-main>` コンポーネント: メニューの高さを200pxに指定。

コードを追加してアバターアイコンをクリックすると、以下のような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/1d405aa9-6c6d-6c65-ca86-078926b79e88.png)

「プロジェクト」ボタンをクリックすると、URLの末尾に「/project」が追加され、プロジェクトページに遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/2a1c7bdd-bd7c-f69c-00cb-93c62e9c7df4.png)
他のボタンでも同様です。

#### 「投稿する」ボタンの追加
次に「投稿する」ボタンを追加していきます。
app.vueに以下の`<v-btn>`タグの中身を追加してください。
```javascript:app.vue
<script setup>
  //省略
</script>
<template>
  <v-app>
    <v-app-bar :style="{ height: '80px' }" elevation="0" class="px-4 pt-1">
      <template v-slot:prepend>
        //省略
      </template>
      <template v-slot:append>
          <v-text-field :style="{ width: '280px' }" prepend-inner-icon="mdi-magnify" label="ページ内を検索" hide-details density="compact"/>
          <v-btn icon class="ml-2"><v-icon>mdi-bell</v-icon></v-btn>
          <v-menu offset-y>
            //省略
          </v-menu>
        //v-btnタグの追加
          <v-btn class="ml-4" color="#55c500" variant="flat">
            <p class="text-white font-weight-bold">
            投稿する
            </p>
            <v-icon end class="text-white">mdi-pencil</v-icon>
          </v-btn>
      </template>
    </v-app-bar>
    <v-main>
      <NuxtPage/>
    </v-main>
  </v-app>
</template>
```
「投稿する」のテキストの右にmdiアイコンでペンのマークを表示させています。

コードを追加すると、以下のような画面が表示され、「投稿する」ボタンが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/2e38a6a7-a6f5-3e94-c68b-33bb45d95eb3.png)


これで、Nuxt3 × Vuetify3でQiita風のヘッダーができました。
各種機能は、各自の環境に合わせて実装してください。

## おわりに
今回は、Nuxt3 × Vuetify3でQiita風のヘッダーを作成してみました。
Vuetifyを使えば、他のWebサイトやWebアプリケーションを簡単に写経することができそうですね。

最後までお読みいただき、ありがとうございました！

記事に関する質問等ございましたら、コメントまたは以下のDMにてよろしくお願いします！

https://twitter.com/t___tatsuki

## 参考文献

https://vuetifyjs.com/en/