WEBサイトでよくある実装ですが、スマホ表示の時にいわゆる「ハンバーガーボタン」などを作ってメニューを隠し、タップすると隠していたメニューを大きく開くような「ドロワーメニュー(Drawer Menu)」と呼ばれるメニュー。
スマホサイトでよく使われる手法ですね。今回はその実装方法をメモ。
まず実際の画面はこちら。
一応、スマホでもPCでも動くように作っています。
Githubにソースコードを置いてますのでコードを詳しく見たい方はどうぞ。
https://github.com/inos3910/drawer-menu
ヘッダーの中にハンバーガーボタンとメニューを用意します。
HTMLの書き方等に特に必須条件はないです。ボタンを置く位置もデザインによって様々なので自由に配置して大丈夫です。今回のデモではヘッダーの中にボタンとメニューを配置するデザインにしたのでこのようなコードになりました。
次にJSを先に書きます。
JSの内容はかなり簡単で、ハンバーガーボタンをタップすると<body>
要素に.is-menu-active
というclassを付けたり外したりする、というだけの実装です。
ひとまず完成したJSがこちら。
ES6でモジュール化してWebpackでバンドルするようにしているので4つのファイルに分割しています。
簡単と言う割にはちょっと長いなって感じですね。笑
長くなっている理由は「PCとタブレットやスマホなどのタッチデバイスの切り分け」です。タッチデバイスにはtouchstart
イベントを、マウスカーソルを操作するPCデバイスにはclick
イベントを使うようにします。
具体的にはtouchEventHandler()
という関数を書いて、その中で条件判定しています。
この関数の中で、要素.addEventListener
ではなくdocument.addEventListener
にして引数の中でわざわざタップした要素を判定しています。
その理由としては、要素.addEventListener
にしていると、開発時に画面サイズをPCとスマホで頻繁に切り替えていたら時々タップやクリックが反応しなくなる現象が起こるからです。
そもそもPC上でスマホサイズにしたりPCサイズにしたりする人なんて開発者しかいないので無視してもいいかもしれませんし、各画面サイズに合わせた時にリロードしたら治るんですが、個人的に気持ち悪かったのでこういう書き方にしたら治りました。
少し脱線しましたが、このようにタップとクリックを切り分けるとスマホでタップした時の反応速度が若干変わります。
もちろんtouchstart
は使わずclick
イベントだけでも動くので、タップの反応速度よりコードを短くして見通しを良くしたいっていう方は、下記のようにコンパクトにclick
イベントだけ書いて動かすことも可能です。
jQueryあり・なしのサンプルですが、やっぱりjQuery楽。笑
自分が実装する時は、よっぽど軽量化が必要なサイト以外はjQuery入れてるので大体これです。実際のところtouchstartイベントとclick
でそこまでタップの反応速度が劇的には変わらないので、コードの見通しを優先することが多いです。
経験上、click
の方がバグも少ないです。
この実装はCSSが1番のポイントです。
JSでクラスの付け替えが実装できれば、あとはCSSが上手に書ければ応用でメニューのデザインは無限に変えられます。
ひとまずデモと同じ横からスライドして出てくるメニューを実装します。
まずは「ドロワーメニュー」の開いた時の状態をSassを使ってコーディングします。CSS設計はflocssを使ってます。
ここでのポイントはz-index
の値です。
レイヤーの重なり順の概念を理解していないとなかなか難しいですが、position
を指定した要素同士で値が大きい方が上に重なります。ここではposition
を指定している「ヘッダー」「ハンバーガーボタン」「メニュー」の中で、ハンバーガーボタンは常に1番上に来るようにして、その下のレイヤーにメニュー、その下にヘッダー、その下にコンテンツ部分、という重なり順になるようにしています。
こうすることで、メニューを表示した時にハンバーガーボタン以外のエリアを全て覆い隠すようなデザインになります。
続いて、閉じた状態でボタンを押すと右側からヌルッと出てくるようにアニメーションさせたいので、transition
を設定してメニューを隠します。加えて、.is-menu-active
が<body>
要素に付いた時にメニューが開くように実装します。
ポイントは親要素の.l-cover
にoverflow:hidden;
を追記して画面外に動かしたメニューを完全に見えないように隠してしまうことです。overflow:hidden;
を書かなかったら変な横スクロールが生まれてしまいます。
その他にハンバーガーボタンも同じ要領で、.is-menu-active
が要素に付いた時に×ボタンに変わるように実装しています。
ここまででメニューは動くのですが、もう少し挙動を安定させたい時に指定しておくと良いかもしれないCSSについてメモしておきます。
htmlにこのように指定するだけで「ダブルタップでズームする」デフォルトの機能を無効にできます。MDNのCSSリファレンスにはダブルタップの挙動以外に下記のようなことも記載されています。
ダブルタップでズームすることを無効にすることで、ユーザーが画面をタップしたとき、ブラウザーがクリックイベントの生成を待つ必要がなくなります。
https://developer.mozilla.org/ja/docs/Web/CSS/touch-action#manipulation
ということは、タップしたときの反応も若干早くなるかもしれません。
ちなみにデモサイトで指定しているのですが、iOS13.7でSafariで見るとダブルタップが普通に効いてます。意味ないのかも・・・。笑
pointer-events
https://developer.mozilla.org/ja/docs/Web/CSS/pointer-events
このCSSはリンクやボタン、JSでクリックやホバーなどのイベントを登録している要素などのイベントをすべて無効にできます。これを使うとリンクも機能しなくなりますし、ボタンも押せなくなります。
具体的にどこに使うかと言うと、隠れている時のメニューは反応して欲しくないのでまずそこが1つ。
あともう1つが、細かいですが「ハンバーガーボタン」の三本線の<span>
要素です。
position:absolute;
でボタンの上に載せているので、タップする時に線の方にフォーカスされて反応が遅れたり反応しなかったりする場合があるかもしれないので、念のため書いておいてもいいと思います。
user-select
https://developer.mozilla.org/ja/docs/Web/CSS/user-select
このCSSは、指定するとスマホの長押しやPCのマウスカーソルで引っ張って選択するアクションができなくなります。
デモサイトではメニュー内のaタグとか、ハンバーガーボタンに指定しています。
このCSSに関しては、使いどころがサイトによってかなり異なります。
例えば今回のようにヘッダー固定してスクロール時に画面に追従するようなものを作っている場合に起こりがちなのですが、ボタンを押した時やスワイプした時にちょっと長押しっぽくなっただけで下に重なった画像が選択されてフォーカスされてしまったり、メニュー内の文字や画像が選択されてメニューを閉じても選択エリアの表示だけが画面上に残ったり、といった不具合ではないけど、操作してる側がうっとうしいなと感じることをクリアにできるので、使いすぎは良くないですが要所要所で使うことをオススメします。
ここまでやればけっこういい感じになると思います!
最初のデモ以外に2パターン作ってみました。
正直そんなに変化ないですかね。笑
案件によって表示方法を変えてみたりしますが、どれも結局は最初のデモの応用でclassの付け替えさえできればあとはCSSですべてまかなえます。
全てGithubに上げてますので気になる方はご覧ください。
https://github.com/inos3910/drawer-menu
最近のドロワーメニューはほとんどclassの付け替えの手法だけで実装しています。もちろんたまにGSAPのようなアニメーションライブラリを使って、もう少し凝ったものを実装することもあります。
ただ結局アニメーションはよっぽどサイト全体で練られていないと、作る側がいくらこだわってもクライアントにもユーザーにもそんなに響かないかむしろ邪魔になってしまう部分なので、最近はサクッと作れてタップの反応も良いこの手法が1番効率が良い気がしています。
前より便利なCSSが使えるようになってきてJSで制御する部分がかなり減ったこともこの手法が使いやすくなった理由の1つですね。
jQueryなしでもOKなので、ドロワーメニューをさくっと自作したい方にはオススメです!