よくある3本線のハンバーガーボタンとそれによって開くメニューを、html、css、JSを使って作ってみたやつです。
codepenにしたためてあったのを今更出す感じなので、実際作ったのは何年も前ですが・・・
概要
よくある3本線のボタンで、押すとメニューが画面全体に広がるものを想定しています。
- クリックするとメニューが開いて、開いている間は閉じるボタン(×)の状態に変わる
- メニューは縦に並び、画面内の空いたスペースには半透明の黒の背景が入る(その部分をクリックしても閉じる)
- メニューが画面外に出た場合はスクロールできる
といった仕様で作っていきます。
コード解説
まずは全体像から。
See the Pen Hamburger by Yoriyasu Nishimura (@yori3) on CodePen.
ハンバーガーボタン
ハンバーガーボタンのhtmlは以下の部分です。
<div class="headerNavToggle"><button class="headerNavToggle__btn"></button></div>
.c-headerNavToggleのdivがボタン全体の丸い部分、buttonタグがハンバーガーの棒の部分を作っています。
cssではbuttonタグのbefore、after擬似要素で上下の棒を作り3本線を形成しています(以下)。
棒の位置を決めるのにtranslateを使ってるのは、後でアニメーションで動かすときに動かしやすいためです。
〜〜略〜〜
display: block;
position: relative;
width: 30px;
flex-basis: auto;
height: 2px;
background-color: #333;
&::before,&::after{
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: inherit;
background-color: #333;
border-radius: inherit;
content: "";
}
&::before{
transform: translateY(-10px);
}
&::after{
transform: translateY(10px);
}
〜〜略〜〜
アニメーションはJSでis-open、is-closeクラスを付けたり外したりして操作しています。
is-close クラスなくても、デフォルトにアニメーション設定したらってお声もあるかもですが、そうするとページ開いたタイミングでもアニメーションが発火してちょっとかっこ悪いのでこうしてます。
アニメーションでは、上下の棒(before、after擬似要素)の位置と角度を変更することで、×の形になるようにしています。
やり方はいろいろあると思うんですが、個人的にこのスタイルが色々な形やアニメーションの要望に対応しやすかったので、基本的にはこれで対応してます。
〜〜略〜〜
&.is-open{
.headerNavToggle__btn{
background-color: transparent;
&::before{
background-color: #333;
transform: translateY(0) rotate(45deg);
animation: menuBtnTopOpen .5s;
}
&::after{
background-color: #333;
transform-origin: center center;
transform: translateY(0) rotate(-45deg);
animation: menuBtnBottomOpen .5s;
}
}
}
〜〜中略〜〜
@keyframes menuBtnTopOpen{
0%{
transform: translateY(-10px) rotate(0);
}
50%{
transform: translateY(0) rotate(0);
}
100%{
transform: translateY(0) rotate(45deg);
}
}
〜〜略〜〜
メニュー本体
メニュー本体のhtmlは以下の通りです。
かなりdivの重ねがけになってます。
<div class="header__nav">
<div class="headerNav">
<div class="headerNav__inner">
<div class="headerNavList">
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page A</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page B</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page C</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page D</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page E</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page F</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page G</a></div>
<div class="headerNavList__item"><a href="" class="headerNavList__link">Page H</a></div>
</div>
</div>
</div>
</div>
.header__navはヘッダー内でのレイアウト用。今回はpositionで位置決めしています。
あと、ヘッダー内においては画面幅いっぱい表示で、後ろに黒の半透明を敷くので、その部分もこのクラスで定義しています。
〜〜略〜〜
&__nav{
position: absolute;
right: 0;
top: 0;
width: 100vw;
height: 100vh;
padding-top: 100px;/* headerの高さに合わせています */
pointer-events: none;
transition: .5s;
transform: translateY(-100%);
z-index: 5000;
&::after{
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(#000,.5);
z-index: -1;
opacity: 0;
content: "";
}
&.is-open{
transform: translateY(0);
pointer-events: auto;
&::after{
transition: .5s;
transition-delay: .5s;
opacity: 1;
}
}
}
〜〜略〜〜
.headerNavはメニューそのものの大枠になります。
レイアウトと分けたのは、他の箇所(例えばサイドバーやフッターなど、背景や100%表示はいらないところ)で同じメニューを使いたい場合、このdivタグから中だけを持っていけば、その場所でも使うことができるようにという考えからです。
なのでこのクラスにかかるスタイルもヘッダー内でしか使わなさそうなheightやoverflowなどは違う部分で適用しています。
〜〜略〜〜
&__nav{
〜〜中略〜〜
.headerNav{
max-width: 400px;
height: 100%;
margin-left: auto;
background-color: #f5f5f5;
pointer-events: auto;
overflow-y: scroll;
overflow-x: hidden;
overscroll-behavior: contain;
}
}
〜〜略〜〜
.headerNavListとheaderNavList__item、headerNavList__linkはメニューのリストを作るためのクラスとして機能しています。
〜〜略〜〜
.headerNavList{
&__item{
border-bottom: 1px solid #ccc;
}
&__link{
display: flex;
align-items: center;
gap: 1em;
padding: 1em;
font-weight: bold;
}
}
〜〜略〜〜
メニューを開いているときの背景スクロールの停止
JavaScriptで操作しています。
メニューを開いたときにbodyタグにposition:fixedを当てることでコンテンツ部分のスクロールを禁止しています。
それだけだと、開くたびにページトップに戻ってしまうので、scrollpos変数に開く前のスクロール位置の情報を入れることで、閉じたときにその位置に戻れるようになっています。また、その値をbodyタグのtopの値にすることで開いたときにコンテンツの位置がずれて見えないようにしています(bodyタグ内全体がスクロール値分上に上がるので、見かけ上は画面がずれてないように見える)。
let scrollpos = 0;
//開いたとき
scrollpos = window.scrollY; //・・・開くときの画面のスクロール位置を取得
bodyTag.classList.add('is-fixed'); //・・・bodyタグにis-fixedをつける(position:fixed;とかの設定)
bodyTag.setAttribute('style','top:'+-scrollpos+'px'); //・・・開くときの画面のスクロール位置の負の値をtopに設定
//閉じたとき
bodyTag.classList.remove('is-fixed'); //・・・is-fixedクラスをとる
bodyTag.setAttribute('style','top:0'); //・・・topの値を0に戻す
window.scrollTo( 0 , scrollpos ); //・・・画面のスクロール位置を保存していたスクロール位置に戻す
まとめ
よくあるハンバーガーボタンを作ってみました。
ボタンの組み方だったり、中のメニューの組み方だったり、色々やり方はあると思うんですが、いくつかサイト制作を経験する中で、この組み方が色々なパターンに対応できてそうな感じがしてます。
必要な要素は押さえられているかと思うので、あとはサイトに合わせてカスタマイズして使えると思います。