Nuxt.js(Composition API)で簡単ハンバーガーメニュー実装
今回の題材
Nuxt.js / Composition API / Tailwind CSS で架空のカフェサイトを題材にレスポンシブな ハンバーガーメニュー を実装してみます。
目次
1. 前提の確認
2. 新規プロジェクトの作成
3. プロジェクトのセットアップ
4. ナビゲーションのマークアップ
5. Tailwind CSSのユーティリティクラスの追加
6. Tailwind CSSのデフォルトのブレイクポイントの設定を変更
7. ハンバーガーメニューのSVG(3本線とバツボタン)の制御処理の実装
8. ハンバーガーメニューの開閉処理の実装
完成コード
完成アプリケーション
1. 前提の確認
- Macであること
- Node.jsがローカルで動作出来る環境にあること
- Nuxt.jsは従来の Options API でなく Composition API で実装する
2. 新規プロジェクトの作成
新規プロジェクトを作成します。
UIフレームワークはTailwind CSSで、他の部分についてはお好みで大丈夫です。
$ npx create-nuxt-app nuxt-nav-sample
create-nuxt-app v3.6.0
✨ Generating Nuxt.js project in nuxt-nav-sample
? Project name: nuxt-nav-sample
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Tailwind CSS
? Nuxt.js modules: Axios - Promise based HTTP client
? Linting tools: ESLint, Prettier
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: GitHub Actions (GitHub only)
? What is your GitHub username? shigeyuki-fukuda
? Version control system: Git
warning ../package.json: No license field
warning nuxt > @nuxt/babel-preset-app > core-js@2.6.12: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your de
pendencies to the actual version of core-js@3.
warning nuxt > @nuxt/webpack > webpack > watchpack > watchpack-chokidar2 > chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependenci
es.
warning nuxt > @nuxt/webpack > webpack > watchpack > watchpack-chokidar2 > chokidar > fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binarie
yarn run v1.22.10
warning ../package.json: No license field
$ eslint --ext ".js,.vue" --ignore-path .gitignore . --fix
✨ Done in 2.51s.
🎉 Successfully created project nuxt-nav-sample
To get started:
cd nuxt-nav-sample
yarn dev
To build & start for production:
cd nuxt-nav-sample
yarn build
yarn start
To test:
cd nuxt-nav-sample
yarn test
For TypeScript users.
See : https://typescript.nuxtjs.org/cookbook/components/
問題ないかサーバーを起動して確認します。
$ yarn run dev
yarn run v1.22.10
warning ../package.json: No license field
$ nuxt
╭───────────────────────────────────────╮
│ │
│ Nuxt @ v2.15.4 │
│ │
│ ▸ Environment: development │
│ ▸ Rendering: client-side │
│ ▸ Target: static │
│ │
│ Listening: http://localhost:3000/ │
│ │
╰───────────────────────────────────────╯
ℹ Preparing project for development 17:07:52
ℹ Initial build may take a while 17:07:52
ℹ Discovered Components: .nuxt/components/readme.md 17:07:52
✔ Builder initialized 17:07:52
✔ Nuxt files generated 17:07:52
✔ Client
Compiled successfully in 3.28s
ℹ Waiting for file changes 17:07:56
ℹ Memory usage: 170 MB (RSS: 329 MB) 17:07:56
ℹ Listening on: http://localhost:3000/ 17:07:56
No issues found.
http://localhost:3000/
にアクセスして以下の画面が表示出来ればOKです🙆♀️
Vue / Nuxtのバージョンを確認しておきます。
このチュートリアルを進める中で正しく動かない?などあればバージョンの差異かもしれないので、念の為🙏
バージョンは以下の通りです。
$ npm list vue
nuxt-nav@1.0.0 /Users/fuqda/dev/nuxt-nav-sample
├─┬ @nuxtjs/composition-api@0.23.3
│ ├─┬ @nuxt/vue-app@2.15.4
│ │ ├── vue@2.6.12 deduped
│ │ └─┬ vuex@3.6.2
│ │ └── vue@2.6.12 deduped
│ ├─┬ @vue/composition-api@1.0.0-rc.7
│ │ └── vue@2.6.12 deduped
│ └── vue@2.6.12
├─┬ @vue/test-utils@1.1.4
│ └── vue@2.6.12 deduped
├─┬ nuxt@2.15.4
│ └─┬ @nuxt/vue-renderer@2.15.4
│ └── vue@2.6.12 deduped
└─┬ vue-jest@3.0.7
└── vue@2.6.12 deduped
$ npm ls typescript
nuxt-nav@1.0.0 /Users/fuqda/dev/nuxt-nav-sample
├─┬ @nuxt/typescript-build@2.1.0
│ ├─┬ ts-loader@8.2.0
│ │ └── typescript@4.2.4 deduped
│ └── typescript@4.2.4
├─┬ @nuxtjs/eslint-config-typescript@6.0.0
│ └─┬ @typescript-eslint/eslint-plugin@4.22.0
│ └─┬ tsutils@3.21.0
│ └── typescript@4.2.4 deduped
└─┬ ts-jest@26.5.5
└── typescript@4.2.4 deduped
今回のプロジェクトディレクトリで使用するNodeのバージョンも念のため指定しておきましょう。
※ nodenv local バージョン
を実行すると実行ディレクトリに .node-version
というファイルが作成されます。
$ nodenv local 15.12.0
3. プロジェクトのセットアップ
@nuxtjs/composition-api
のインストール
Nuxt.jsでComposition APIを利用するためにプラグインを追加します。
$ yarn add @nuxtjs/composition-api
yarn add v1.22.10
warning ../package.json: No license field
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
warning "@nuxt/types > sass-loader@10.1.1" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".
warning "@nuxt/typescript-build > ts-loader@8.2.0" has unmet peer dependency "webpack@*".
warning "@nuxtjs/eslint-config-typescript > @typescript-eslint/eslint-plugin > tsutils@3.21.0" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta".
warning "@nuxtjs/eslint-module > eslint-webpack-plugin@2.5.4" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "@nuxtjs/tailwindcss > @nuxt/postcss8 > css-loader@5.2.4" has unmet peer dependency "webpack@^4.27.0 || ^5.0.0".
warning "@nuxtjs/tailwindcss > @nuxt/postcss8 > postcss-loader@4.2.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning " > @vue/test-utils@1.1.4" has unmet peer dependency "vue@2.x".
warning " > @vue/test-utils@1.1.4" has unmet peer dependency "vue-template-compiler@^2.x".
warning " > babel-core@7.0.0-bridge.0" has unmet peer dependency "@babel/core@^7.0.0-0".
warning " > babel-jest@26.6.3" has unmet peer dependency "@babel/core@^7.0.0".
warning "babel-jest > babel-preset-jest@26.6.2" has unmet peer dependency "@babel/core@^7.0.0".
warning "babel-jest > babel-preset-jest > babel-preset-current-node-syntax@1.0.1" has unmet peer dependency "@babel/core@^7.0.0".
warning "babel-jest > babel-preset-jest > babel-preset-current-node-syntax > @babel/plugin-syntax-bigint@7.8.3" has unmet peer dependency "@babel/core@^7.0.0-0".
warning "babel-jest > babel-preset-jest > babel-preset-current-node-syntax > @babel/plugin-syntax-import-meta@7.10.4" has unmet peer dependency "@babel/core@^7.0.0-0".
warning " > ts-jest@26.5.5" has unmet peer dependency "typescript@>=3.8 <5.0".
warning " > vue-jest@3.0.7" has unmet peer dependency "vue@^2.x".
warning " > vue-jest@3.0.7" has unmet peer dependency "vue-template-compiler@^2.x".
warning "@nuxtjs/composition-api > @vue/composition-api@1.0.0-rc.7" has unmet peer dependency "vue@>= 2.5 < 3".
warning " > @nuxtjs/composition-api@0.23.3" has unmet peer dependency "@nuxt/vue-app@^2.15".
warning " > @nuxtjs/composition-api@0.23.3" has unmet peer dependency "fs-extra@^8.1.0".
warning " > @nuxtjs/composition-api@0.23.3" has unmet peer dependency "vue@^2".
[4/4] 🔨 Building fresh packages...
success Saved lockfile.
success Saved 5 new dependencies.
info Direct dependencies
└─ @nuxtjs/composition-api@0.23.3
info All dependencies
├─ @nuxtjs/composition-api@0.23.3
├─ @vue/composition-api@1.0.0-rc.7
├─ estree-walker@2.0.2
├─ magic-string@0.25.7
└─ sourcemap-codec@1.4.8
✨ Done in 5.66s.
nuxt.config.js
への定義追加
nuxt.config.js
にも定義を追加します。
buildModules: [
// https://go.nuxtjs.dev/typescript
'@nuxt/typescript-build',
+ '@nuxtjs/composition-api/module',
],
※ '@nuxtjs/composition-api
と書くとエラーになるので注意です😅
/pages/index.vue
のデフォルトのテンプレートの各要素を削除
一旦、 /pages/index.vue
から初期状態の無駄な要素を削除して真っ新な状態にします。
<template>
<div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({})
</script>
4. ナビゲーションのマークアップ
ナビゲーション用のコンポーネントを追加します。
/components/Nav.vue
<template>
<div>
<header>
<div>
<div>
<h1>
<nuxt-link to="/">Nuxt Cafe</nuxt-link>
</h1>
<div>
<!-- TODO: ハンバーガーメニュー実装 -->
</div>
</div>
<nav>
<ul>
<li>
<nuxt-link to="/shop">店舗情報</nuxt-link>
</li>
<li>
<nuxt-link to="/menu">メニュー</nuxt-link>
</li>
<li>
<nuxt-link to="/information">お知らせ</nuxt-link>
</li>
</ul>
</nav>
</div>
</header>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
name: 'Nav',
setup() {
return {}
},
})
</script>
/pages/index.vue
トップページに新規作成したコンポーネントを追加します。
<template>
<Nav/>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
import Nav from '@/components/Nav.vue'
export default defineComponent({
components: { Nav }
})
</script>
今時点の画面がこんな感じです。
5. Tailwind CSSのユーティリティクラスの追加
テンプレートにTailwind CSSのユーティリティクラスを追加していきます。
/components/Nav.vue
<template>
<div>
<header class="w-full absolute md:static bg-black px-2 py-2 z-50">
<div class="md:max-w-3xl mx-auto md:flex md:items-center">
<div
class="w-full md:flex mx-auto px-6 md:px-0 flex justify-between items-center h-16"
>
<h1>
<nuxt-link
class="text-white text-sm font-bold leading-relaxed inline-block mr-4 py-2 whitespace-no-wrap"
to="/"
>
Nuxt Cafe
</nuxt-link>
</h1>
<div class="text-white md:hidden">
<button class="focus:outline-none">
<!-- ハンバーガーメーニューのSVG:https://reffect.co.jp/html/tailwind-for-beginners-navigation-menu -->
<svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
<path
d="M24 6h-24v-4h24v4zm0 4h-24v4h24v-4zm0 8h-24v4h24v-4z"
/>
</svg>
</button>
</div>
</div>
<nav
class="w-full md:block absolute left-0 md:static bg-black md:bg-none z-20"
>
<ul class="md:flex md:justify-end md:items-end">
<li class="w-full md:w-auto md:ml-5">
<nuxt-link
to="/shop"
class="text-white sm:hover:bg-gray-600 md:block inline-block md:py-0 py-5 px-5 md:px-0 w-full"
>店舗情報</nuxt-link
>
</li>
<li class="w-full md:w-auto md:ml-5">
<nuxt-link
to="/menu"
class="text-white sm:hover:bg-gray-600 md:block inline-block md:py-0 py-5 px-5 md:px-0 w-full"
>メニュー</nuxt-link
>
</li>
<li class="w-full md:w-auto md:ml-5">
<nuxt-link
to="/information"
class="text-white sm:hover:bg-gray-600 md:block inline-block md:py-0 py-5 px-5 md:px-0 w-full"
>お知らせ</nuxt-link
>
</li>
</ul>
</nav>
</div>
</header>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
name: 'Nav',
setup() {
return {}
},
})
</script>
表示はこんな感じです。
モバイルサイズでの表示の場合にハンバーガーメニューのリンクだけでなく中身のメニューまで見えてしまっていますが、表示制御は後のセクションで行います。
6. Tailwind CSSのデフォルトのブレイクポイントの設定を変更
ハンバーガーメニューを表示する際にPCサイズとモバイルサイズで表示切り替えするためにTailwind CSSのブレイクポイントの設定をいじりたいので、設定ファイルを生成します。
$ npx tailwindcss init
tailwindcss 2.1.2
✅ Created Tailwind config file: tailwind.config.js
tailwind.config.js
767px以下をスマホサイズ、 768px以上~1023px以下を通常のPCサイズ、1024px以上~1279px以下をモニターなどを利用した際の大きな画面サイズとして定義しておきます。
module.exports = {
purge: [],
darkMode: false, // or 'media' or 'class'
theme: {
screens: {
sm: { max: '767px' },
md: { min: '768px' },
lg: { min: '1024px' },
xl: { min: '1280px' },
},
},
variants: {
extend: {},
},
plugins: [],
}
7. ハンバーガーメニューのSVG(3本線とバツボタン)の制御処理の実装
/components/Nav.vue
setup
関数内に表示制御用の値とメソッドを定義します。
<script lang="ts">
+ import { defineComponent, ref } from '@nuxtjs/composition-api'
export default defineComponent({
name: 'Nav',
setup() {
- return {}
+ const showMenu = ref(false)
+ const toggleStatus = () => {
+ showMenu.value = !showMenu.value
+ }
+ return {
+ showMenu,
+ toggleStatus
+ }
},
})
</script>
次に <template>
側も修正していきます。
<div class="text-white md:hidden">
+ <button class="focus:outline-none" @click="toggleStatus">
- <button class="focus:outline-none">
<!-- ハンバーガーメーニューのSVG:https://reffect.co.jp/html/tailwind-for-beginners-navigation-menu -->
<svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
<path
+ v-show="!showMenu"
d="M24 6h-24v-4h24v4zm0 4h-24v4h24v-4zm0 8h-24v4h24v-4z"
/>
+ <path
+ v-show="showMenu"
+ d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"
+ />
</svg>
</button>
</div>
これでモバイルサイズでハンバーガーメニューをクリックした後にバツボタンに変わり、もう一度クリックすると通常のハンバーガーメニューに戻るようになりました。
8. ハンバーガーメニューの開閉処理の実装
/components/Nav.vue
モバイルサイズの時にデフォルトでメニュー内容が表示されないように sm:hidden
を nav
要素のclassに追加します。
<nav
- class="w-full md:block absolute left-0 md:static bg-black md:bg-none z-20"
+ class="w-full sm:hidden md:block absolute left-0 md:static bg-black md:bg-none z-20"
>
最後にモバイルサイズの時はメニュー内容を隠して、ハンバーガーメニュー押下時に内容を出すように三項演算子での分岐を追加します。
<nav
- class="w-full sm:hidden md:block absolute left-0 md:static bg-black md:bg-none z-20"
+ :class="(showMenu) ? 'sm:block' : 'sm:hidden'"
+ class="w-full md:block absolute left-0 md:static bg-black md:bg-none z-20"
>
これで完成です🎉
Discussion