まずはメニューのタイトルを作る
いきなりメニューのリストを隠したり表示させたりする機能を作り上げようとすると大変なので、まずはメニューのタイトル部分から作成していきます。
今回は、『MENU』のテキストと+(プラス)マークをメニューのタイトルとして表示させておき、要素がクリックされたら+(プラス)マークが-(マイナス)マークに切り替わるアニメーション を設定します。
単に表示を切り替えたい場合は、画像を用意して表示画像を切り替えても良いのですが、今回はアニメーションさせたいので、プラス&マイナスマークはCSSで表現する必要があります。
サンプルでは疑似要素(::before/::after) を使い、幅と高さを与えた上でbackground-colorで背景色を指定することで、プラスマーク状態を表現しました。
縦線or横線のどちらかを作ってしまえば、もう片方はそれをtransformで90度回転させる ことで簡単にプラスマークを作ることができます。
box-sizingは、全ての要素でborder-boxであるものとします
< nav class ="menu_outer" >
< div class ="menu_index" >
MENU
< div class ="toggle_btn" ></ div >
</ div >
</ nav >
▼
.menu_index {
width : 250px ;
padding : 10px ;
display : flex ;
justify-content : space-between ;
align-items : center ;
position : relative ;
z-index : 2 ;
background-color : white ;
border : solid 1px ;
cursor : pointer ;
}
.toggle_btn {
position : relative ;
width : 1rem ;
height : 1rem ;
}
.toggle_btn::before {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
}
.toggle_btn::after {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
transform : rotate ( 90deg ) ;
}
▼
現時点ではクリックしても何も起こりませんが、メニューのタイトル部分の見た目が出来上がりました。
なお、後々のことを考慮してz-index で重なり順を指定しているところもありますが、この段階では特に気にする必要はありません。
あとはトグルボタンの役割を果たす要素(toggle_btn)にactiveというclassが付与されたら、プラスからマイナスマークになるようにCSS調整を追加し、JavaScriptでクリックイベントを設定すれば完成です。
::beforeと::afterの両方にtransitonプロパティを指定する のを忘れないようにしましょう。
プラスとマイナスマークを切り替える動きは、90度回転させてプラスマークの縦線にしているもの(::after)を0度に戻して横線(マイナス)にし、プラスマークの横線(::before)を移動させながら透明にすることで表現できます。
.menu_index {
width : 250px ;
padding : 10px ;
display : flex ;
justify-content : space-between ;
align-items : center ;
position : relative ;
z-index : 2 ;
background-color : white ;
border : solid 1px ;
cursor : pointer ;
}
.toggle_btn {
position : relative ;
width : 1rem ;
height : 1rem ;
}
.toggle_btn::before {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
transition : .2s ;
}
.toggle_btn.active::before {
transform : translateX ( -10px ) ;
opacity : 0 ;
}
.toggle_btn::after {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
transform : rotate ( 90deg ) ;
transition : .2s ;
}
.toggle_btn.active::after {
transform : rotate ( 0deg ) ;
}
▼
// MENUのindex(div要素)を取得
const menuIndex = document .querySelector ( ".menu_index" ) ;
// トグルボタン要素を取得
const toggleBtn = document .querySelector ( ".toggle_btn" ) ;
// MENUのindex(div要素)がクリックされたら
menuIndex .addEventListener ( "click" , () => {
// トグルボタン要素にactiveクラスを付けたり外したりする
toggleBtn .classList .toggle ( "active" ) ;
} ) ;
▼
クリックするたび、+(プラス)マークと-(マイナス)マークが切り替わるメニュータイトルが出来上がりました!
今回はプラス・マイナスマークの切り替えでメニューの開閉状態を表現しますが、他にも不等号のような形のマーク(>)を使ってメニューの開閉を表すパターンもあります。
アコーディオンメニューのマークに決まりがあるわけではありませんが、オシャレかどうかではなく、分かりやすいかどうかを最優先 にしてUIデザインを決定すると良いでしょう。
次のステップでは、HTMLとCSSでメニューが表示された状態を用意していきます。
POINT!
・プラスマークをCSSで表現したい場合は、疑似要素をうまく活用しよう!
・プラスマークの二本の線のうち、片方を90度回転させれば簡単にできる!
・アコーディオンメニューの開閉状態を表すマークは、直感的に分かりやすいものを採用しよう!
メニューリスト部分を整えておく
メニュータイトルの部分が出来上がったので、次はメニューリストの部分を整えていきましょう。
アコーディオンにすることはとりあえず置いておき、まずはメニューの見た目を調整していきます。
通常、メニューリストは<a>タグを使って該当のページに飛べるようにリンクを設定する必要がありますが、今回は構造をシンプルにするためリンク設定は省略 します。
ということで、<ul>・<li>タグのみでメニューリストをマークアップして、HTMLを整えたものが下のコードです。
< nav class ="menu_outer" >
< div class ="menu_index" >
MENU
< div class ="toggle_btn" ></ div >
</ div >
< ul class ="menu_container" >
< li > HOME</ li >
< li > ABOUT</ li >
< li > WORKS</ li >
< li > CONTACT</ li >
</ ul >
</ nav >
▼
これでHTML部分は完成です。(これ以降、HTMLコードを変更・追加することはありません)
次にCSSでメニューリストの見た目を調整していきますが、こちらもコードをシンプルにするため、リストの見た目をリセットするためのCSSは省略 します。
なので、サンプルと見た目を同じにしたい場合は、たとえば list-style: none; などの調整が必要ですが、その記載は省略されているのでご注意ください。
サンプルでは、メニューリストの幅と境界線がリストタイトルと同じになるように調整した上で、背景色を変えることでコントラストをつけました。
また、メニュー全体をマークアップしているnav要素(menu_outer)に幅と高さが子要素にフィットするよう、fit-content を指定しています。
なお、前のステップで紹介したメニュータイトルに関係するCSS調整の記載は省略しています。
.menu_outer {
width : fit-content ;
height : fit-content ;
}
.menu_container {
width : 250px ;
border : solid 1px ;
border-top : none ;
background-color : lightcyan ;
position : relative ;
z-index : 1 ;
}
.menu_container li {
padding : 10px ;
cursor : pointer ;
}
.menu_container li:hover {
background-color : darkcyan ;
color : white ;
}
▼
メニューリストのborder-topは、メニュータイトルのborder-bottomと被る(共有する)ので必要ありません。
なので、border-top: none; で上部の境界線だけ表示されないように調整 しています。
また、メニューリストの上にマウスカーソルが乗っかった時に分かりやすくなるよう、疑似クラス:hover を利用してマウスホバーアクションも指定しておきました。
アコーディオンメニューの動きとは直接関係ありませんが、分かりやすいUIデザインにする上では重要なポイントです。
当然ですが、現状ではまだメニュータイトルのプラスとマイナスマークが切り替わるだけで、アコーディオンの動きは出来上がっていない状態です。
次のステップでメニューが隠された状態を作り、アコーディオンメニューの完成形に近づけていきます。
POINT!
・メニューリスト部分は、ul・liタグでマークアップしよう(本番はaタグによるリンク設定も必要)!
・リストのスタイルはブラウザの影響を強く受けるので、リセットCSSなどを導入しよう!
・マウスホバーで色を変化させるなど、ユーザーの利便性を高める工夫も大事!
メニューを移動させて隠す
メニューリストができたら、次はtransform: translateY() で要素を移動させて隠していきます。
ただしこのままメニューリストを上に移動させても、当然ですが下のようにはみ出してしまいます。
メニューリストがはみ出てしまう
はみ出た部分を隠す(クリップ)するには、親要素(menu_outer)に overflow: hidden を指定すればOKです。
.menu_outer {
width : fit-content ;
height : fit-content ;
overflow : hidden ;
}
▼
上に移動させた要素がクリップされる
なお、transform: translateY()で要素を上に移動させる距離は、ブラウザで表示結果を確認しながら-200pxというふうに、手動的に調整してもダメではないのですが、この方法には問題があります。
それは、メニュー項目が増えるor減ると要素の縦幅が変わる→そのたびに要素の移動距離を調整し直さなければならなくなるという点 です。
そこで今回はこの問題を解決するために、JavaScriptで要素の縦幅を計測し、その分だけ移動させる という方法でやってみようと思います。
要素の縦幅は、読み取り専用のプロパティであるclientHeight で取得することができます。
なお、clientHeightはborder幅を含めない ため、border幅を含めた距離を移動させたい場合はborder幅を足す必要があります。
以上を踏まえて、JavaScriptコードを確認していきましょう。
// メニューのul要素を取得
const menuContainer = document .querySelector ( ".menu_container" ) ;
// ul要素の縦幅を取得して、translateYで上に移動させておく
// (clientHeightはboreder幅を含めないので、borderの幅を足す)
const menuContainerHeight = menuContainer .clientHeight ;
menuContainer .style .transform = `translateY(- ${ menuContainerHeight + 1 } px)` ;
▼
JavaScriptで計測した距離(メニューリストの縦幅)を上に移動させることで、メニューリストを隠すことができました!
が、この方法ではどうしてもページが開かれた後で要素が移動することになるので、それを隠すためにopacityで不透明度を0 にして見えなくしておきましょう。
ついでにtransition も指定して、transformによる移動がアニメーションになるようにしておきます。
.menu_container {
width : 250px ;
border : solid 1px ;
border-top : none ;
background-color : lightcyan ;
position : relative ;
z-index : 1 ;
opacity : 0 ;
transition : .4s ;
}
▼
あとは、要素がクリックされたらopacityで0にしていた不透明度を1に戻して見えるようにし、translateY()による移動を制御すれば完成です。
最後のステップで、アコーディオンメニューの動きを完成させていきましょう!
POINT!
・親要素のoverflowをhiddenにした上でメニューリストを上に移動させ、メニューの非表示状態を作ろう!
・メニューを移動させる距離をclientHeightで制御しておくと、メニュー項目が増減しても対応できる!
・この方法ではページが開かれた後での移動になるので、最初はopacityを0にするなどして隠しておこう!
アコーディオンメニューの動きを制御する
translateY()で移動させる距離をJavaScriptで管理している以上、プラス&マイナスマークのトグルボタンのように、activeクラスを付けたり外したりするだけではメニューリストの移動を制御できません。
メニューリストの表示・非表示状態のコントロールは、activeクラスが含まれているかどうかをif文で判定 して、条件によってクラス操作とtranslateY()の移動を行うことで実現させます。
つまり、メニューリスト(menu_container)にactiveクラスが含まれていなければ、activeクラスを付与してtranslateY()で移動させていた距離を元に戻し、逆の場合はactiveクラスを外してtranslateY()でメニューが隠れるようにするということです。
特定のクラスが含まれているかどうかの真偽(true/false)判定は、classList.contains()メソッド で行うことができます。
以上を踏まえて、完成版の全体的なコードを確認していきましょう!
< nav class ="menu_outer" >
< div class ="menu_index" >
MENU
< div class ="toggle_btn" ></ div >
</ div >
< ul class ="menu_container" >
< li > HOME</ li >
< li > ABOUT</ li >
< li > WORKS</ li >
< li > CONTACT</ li >
</ ul >
</ nav >
▼
.menu_outer {
width : fit-content ;
height : fit-content ;
overflow : hidden ;
}
.menu_index {
width : 250px ;
padding : 10px ;
display : flex ;
justify-content : space-between ;
align-items : center ;
position : relative ;
z-index : 2 ;
background-color : white ;
border : solid 1px ;
cursor : pointer ;
}
.toggle_btn {
position : relative ;
width : 1rem ;
height : 1rem ;
}
.toggle_btn::before {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
transition : .2s ;
}
.toggle_btn.active::before {
transform : translateX ( -10px ) ;
opacity : 0 ;
}
.toggle_btn::after {
content : "" ;
position : absolute ;
width : 100% ;
height : 1px ;
top : 0 ;
bottom : 0 ;
margin : auto ;
background-color : black ;
transform : rotate ( 90deg ) ;
transition : .2s ;
}
.toggle_btn.active::after {
transform : rotate ( 0deg ) ;
}
.menu_container {
width : 250px ;
border : solid 1px ;
border-top : none ;
background-color : lightcyan ;
position : relative ;
z-index : 1 ;
opacity : 0 ;
transition : .4s ;
}
.menu_container li {
padding : 10px ;
cursor : pointer ;
}
.menu_container li:hover {
background-color : darkcyan ;
color : white ;
}
▼
// MENUのindex(div要素)を取得
const menuIndex = document .querySelector ( ".menu_index" ) ;
// トグルボタン要素を取得
const toggleBtn = document .querySelector ( ".toggle_btn" ) ;
// メニューのul要素を取得
const menuContainer = document .querySelector ( ".menu_container" ) ;
// ul要素の縦幅を取得して、translateYで上に移動させておく
// (clientHeightはboreder幅を含めないので、borderの幅を足す)
const menuContainerHeight = menuContainer .clientHeight ;
menuContainer .style .transform = `translateY(- ${ menuContainerHeight + 1 } px)` ;
// MENUのindex(div要素)がクリックされたら
menuIndex .addEventListener ( "click" , () => {
// トグルボタン要素にactiveクラスを付けたり外したりする
toggleBtn .classList .toggle ( "active" ) ;
// opacityで隠しておいたメニューを表示(以降、opactyは1のままで良い)
menuContainer .style .opacity = 1 ;
// ul要素にacitveクラスが含まれていなければクラスを付与し、transformで移動
if ( !menuContainer .classList .contains ( "active" ) ) {
menuContainer .classList .add ( "active" );
menuContainer .style .transform = "translateY(0)" ;
} else {
// そうでない場合はactiveクラスを外してtransformで移動
menuContainer .classList .remove ( "active" );
menuContainer .style .transform = `translateY(- ${ menuContainerHeight + 1 } px)` ;
}
} ) ;
▼
というわけで、素のJavaScript(Vanilla JS)でアコーディオンメニューを作ることができました!
ライブラリに頼らすにやや複雑な機能を実装したい場合は、ステップを細かく分割するとやりやすい です。
今回の例で言うと、まずメニューのタイトル部分だけを作るところから始めた感じですね。
また、今回のJavaScriptのコードには細かすぎるぐらいコメントを残しておきましたが、慣れないうちはできるだけコメントを残しながらコーディングしていった方が、頭の中を整理できるのでオススメです。
POINT!
・clientHeightで移動距離を管理しているので、activeクラスのつけ外しだけでは制御不可!
・activeクラスの有無によりif文で条件分岐させて、メニューの表示・非表示の処理を分けよう!
・複雑な機能を実装する際は、ステップを細かく分割するとやりやすい!