読者の悩み
- ホバーで出るドロップダウンメニューの作り方がわからない
- アコーディオンメニューのような動きで開くアニメーションにしたい
ホバーでドロップダウンメニューの作り方がわからない...
ホバーをトリガーとしたドロップダウンメニューの作るイメージがわかりにくいですよね。
今回はそのあたりを丁寧に説明させていただきます。
本記事の内容
- ホバーでのドロップダウンメニューの作り方を解説します
- アコーディオンのような動きはCSSのみで実装可能
ホバーでのドロップダウンメニューの作り方を解説します
今回はHTML、CSS、JavaScriptでドロップダウンメニューを作っていきたいと思います。
まずは完成イメージです。
とりあえず、このように動くイメージで作っていきます。
まずはHTMLファイルから作っていきましょう。
HTMLファイルを作っていこう
index.htmlファイルを作り、まずはここまで書いていきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ドロップダウンメニュー</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
</main>
<script src="index.js"></script>
</body>
</html>
bodyタグの中にmainタグを作って、headタグの中にstyles.cssの読込と、bodyの閉じタグの手前にindex.jsの読込を完了しておき、styles.cssとindex.jsのファイルを作っておいてください。
今回はEmmetを使い、一気にHTMLファイルを完成させます。
mainタグの中に、こちらを書いていきます。
ul.menu>li.menu__item*3>a.menu__link{親メニュー$}+ul.drop-menu>li.drop-menu__item*3>a.drop-menu__link{子メニュー$}
こちらをエディターに記入して、エンターで開きます。
index.htmlファイルはこのような感じになったのではないでしょうか?
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ドロップダウンメニュー</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<ul class="menu">
<li class="menu__item">
<a href="" class="menu__link">親メニュー1</a>
<ul class="drop-menu">
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー1</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー2</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー3</a></li>
</ul>
</li>
<li class="menu__item">
<a href="" class="menu__link">親メニュー2</a>
<ul class="drop-menu">
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー1</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー2</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー3</a></li>
</ul>
</li>
<li class="menu__item">
<a href="" class="menu__link">親メニュー3</a>
<ul class="drop-menu">
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー1</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー2</a></li>
<li class="drop-menu__item"><a href="" class="drop-menu__link">子メニュー3</a></li>
</ul>
</li>
</ul>
</main>
<script src="index.js"></script>
</body>
</html>
これで、index.htmlは完成となります。
Emmetを導入されてない人は、すいませんが上記を参考に入力してください。
HTMLのポイントは、親メニューの中に子メニューを含めてしまっていることです。こういうふうに設定することで、親メニューにホバー設定をすることで、子メニューにホバーしていても、子メニューが消えないようになります。
子メニューも親メニューの一部ですからね。
ブラウザで表示をみてみましょう。
表示はこのようになってれば、OKです。
それでは、次はCSSでスタイルをつけていきましょう。
CSSを書いていこう
styles.cssファイルに下記を書いていってください。
*, *::after, *::before {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
a {
color: #333;
text-decoration: none;
}
ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
padding: 0;
margin: 0;
}
main {
width: 100%;
margin: 0px auto 10px;
background-color: lightcyan;
}
.menu {
display: flex;
justify-content: center;
}
.menu__link {
display: block;
padding: 10px 20px;
}
.menu__link:hover {
background-color: #ebebeb;
color: #666;
}
.drop-menu {
position: absolute;
top: 43px;
transition: all .3s;
}
.drop-menu__link {
display: block;
/* display: none; デフォルトの設定は消す */
background-color: #ebebeb;
transition: all .3s;
padding: 5px 20px;
}
.drop-menu__link:hover {
background-color: lightcyan;
}
親メニューと子メニューにスタイルをつけています。
ポイントは、55行目の.drop-menu__linkは子メニューのaタグですが、display: none; でデフォルトでは消していますが、今はみえるようにしています。
ブラウザで表示をみてみましょう。
このようになってればOKです。
表示は確認できたので、55行目の「display: none;」を有効にしてください。
次は、ホバーした時のドロップダウンのスタイルを付けていきます。
styles.cssの1番最後に下記のCSSを追記してください。
/* ドロップダウン出現後のスタイル */
.drop-menu__link.is-active {
display: block;
}
これは、JavaScriptで、.drop-menu__linkに「is-active」クラスを付加して、display: block;で非表示の状態を表示させることができます。
styles.cssの全体はこのようになります。
*, *::after, *::before {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
a {
color: #333;
text-decoration: none;
}
ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
padding: 0;
margin: 0;
}
main {
width: 100%;
margin: 0px auto 10px;
background-color: lightcyan;
}
.menu {
display: flex;
justify-content: center;
}
.menu__link {
display: block;
padding: 10px 20px;
}
.menu__link:hover {
background-color: #ebebeb;
color: #666;
}
.drop-menu {
position: absolute;
top: 43px;
transition: all .3s;
}
.drop-menu__link {
display: none; /* display: block;は削除 */
background-color: #ebebeb;
padding: 5px 20px;
}
.drop-menu__link:hover {
background-color: lightcyan;
}
/* ドロップダウン出現後のスタイル */
.drop-menu__link.is-active {
display: block;
}
これでCSSファイルは完成になります。
これで、一度ブラウザで表示を確認してみましょう。
ドロップダウンメニューが消えましたね。
それでは、次はJavaScriptでドロップダウンの動きをつけていきたいと思います。
JavaScriptでドロップダウンメニューに動きを付けていこう
index.jsファイルにJavaScriptを書いていきます。
JavaScriptを書く手順です。
- 親メニュー(liタグ)を取得
- 親メニュー(liタグ)すべてにクリックイベントを付ける
- イベント発動時の処理を書く
という流れになります。
まずは親メニューの取得ですね。
index.jsファイルに書いていきましょう。
'use strict';
{
// DOM取得
// 親メニューのli要素
const parentMenuItem = document.querySelectorAll('.menu__item');
console.log(parentMenuItem);
}
まずは、querySelectorAllで親メニューのli要素をすべて取得します。
console.logで確認してみましょう。
NodeListで3つのli要素を取得できてますね。
NodeListがわからない人は、下記記事で解説してます。
親メニューのli要素を取得できたので、次はイベントを親メニューのli要素すべてに付けていきたいと思います。
index.jsファイルの「console.log(parentMenuItem);」のすぐ下に追記してください。
// イベントを付加
for (let i = 0; i < parentMenuItem.length; i++) {
parentMenuItem[i].addEventListener('mouseover', dropDownMenuOpen);
}
for文で、親メニューのli要素すべてにイベントを付けていきます。
今回のイベントは、「'mouseover'」です。イベントを付けた要素にマウスカーソルがホバーすると、処理が発動します。
index.jsファイルの全体はこのようになります。
'use strict';
{
// DOM取得
// 親メニューのli要素
const parentMenuItem = document.querySelectorAll('.menu__item');
console.log(parentMenuItem);
// イベントを付加
for (let i = 0; i < parentMenuItem.length; i++) {
parentMenuItem[i].addEventListener('mouseover', dropDownMenuOpen);
}
}
これで、イベント付加まで完了しました。
次はイベント発動時の処理を書いていきます。
index.jsファイルのfor文の下に追記していってください。
// ドロップダウンメニューを開く処理
function dropDownMenuOpen(e) {
// 子メニューa要素
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを付加
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.add('is-active');
}
}
イベント付加の部分で、関数名(dropDownMenuOpen)を指定したので、その関数を別で書いています。
dropDownMenuOpen(e)となっていますが、eにはイベントが発動した要素の情報が入ってきています。
なので、「e.currentTarget」とイベントの中で書くことで、マウスホバーした要素を取得できます。
今回の処理だったら、マウスホバーした親メニューのli要素にquerySelectorAll('.drop-menu__link')しているので、マウスホバーしたli要素の中だけの、aタグを取得できます。
次は、取得したaタグを処理するために、for文の繰り返し処理で、マウスホバーした親メニューのli要素の中のaタグすべてに「is-active」クラスを付けています。
このfunctionですが、別の方法としては、親メニューのli要素ごとに違うクラス名を付けることで、個別で処理できますが、そんなめんどくさいことをするのも嫌ですし、コードがごちゃごちゃになります。
e.currentTargetでホバーした要素を特定する方法だと、急遽メニュー自体が増えた時にも、クラス名をそのままで、簡単にコピペで追加することができますし、コードがシンプルでみやすくなっています。
index.jsファイルの全体をみていきましょう。
'use strict';
{
// DOM取得
// 親メニューのli要素
const parentMenuItem = document.querySelectorAll('.menu__item');
console.log(parentMenuItem);
// イベントを付加
for (let i = 0; i < parentMenuItem.length; i++) {
parentMenuItem[i].addEventListener('mouseover', dropDownMenuOpen);
}
// ドロップダウンメニューを開く処理
function dropDownMenuOpen(e) {
// 子メニューa要素
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを付加
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.add('is-active');
}
}
}
これで、ドロップダウンメニューを開けるようになったと思います。
こんな感じで、ホバーでドロップダウンメニューが出てくれば、OKです。
ドロップダウンが出てくるのはいいですが、ホバーが外れたら、ドロップダウンメニューはまた消えてほしいですよね。
次はホバーが外れた時の処理を書いていきます。
ドロップダウンメニューをホバーが外れると消えるイベントを追加します。
index.jsのイベント付加の部分に、下記の1行を追加します。
parentMenuItem[i].addEventListener('mouseleave', dropDownMenuClose);
ホバーした要素に、'mouseleave'というイベントを付けています。
leaveの意味は「離れる」ですので、ホバーから離れたら発動するイベントとなります。
index.jsファイル全体をみていきましょう。
'use strict';
{
// DOM取得
// 親メニューのli要素
const parentMenuItem = document.querySelectorAll('.menu__item');
console.log(parentMenuItem);
// イベントを付加
for (let i = 0; i < parentMenuItem.length; i++) {
parentMenuItem[i].addEventListener('mouseover', dropDownMenuOpen);
parentMenuItem[i].addEventListener('mouseleave', dropDownMenuClose);
}
// ドロップダウンメニューを開く処理
function dropDownMenuOpen(e) {
// 子メニューa要素
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを付加
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.add('is-active');
}
}
}
イベント付加の中に1行追加しました。
それでは、'mouseleave'イベントの関数を定義していきます。
それでは、index.jsファイルの27行目の部分に「dropDownMenuClose」関数を定義していきます。
// ドロップダウンメニューを閉じる処理
function dropDownMenuClose(e) {
// 子メニューリンク
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを削除
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.remove('is-active');
}
}
関数のスコープの関係で、dropDownMenuOpenの関数内の子メニューリンク(aタグ)部分の変数は持ち出せないので、もう一度同じように、ホバーした要素の子メニューリンク(aタグ)をもう一度、変数に入れ直しています。
そして、ホバーした要素の子メニューリンク(aタグ)をfor文で取り出してます。
ほぼ一緒なんですが、最後のaタグのis-activeクラスをremoveで削除する部分がこのイベントの違いになります。
index.jsファイル全体をみていきましょう。
'use strict';
{
// DOM取得
// 親メニューのli要素
const parentMenuItem = document.querySelectorAll('.menu__item');
console.log(parentMenuItem);
// イベントを付加
for (let i = 0; i < parentMenuItem.length; i++) {
parentMenuItem[i].addEventListener('mouseover', dropDownMenuOpen);
parentMenuItem[i].addEventListener('mouseleave', dropDownMenuClose);
}
// ドロップダウンメニューを開く処理
function dropDownMenuOpen(e) {
// 子メニューa要素
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを付加
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.add('is-active');
}
}
// ドロップダウンメニューを閉じる処理
function dropDownMenuClose(e) {
// 子メニューリンク
const childMenuLink = e.currentTarget.querySelectorAll('.drop-menu__link');
console.log(childMenuLink);
// is-activeを削除
for (let i = 0; i < childMenuLink.length; i++) {
childMenuLink[i].classList.remove('is-active');
}
}
}
これで、マウスホバーが外れるとis-activeクラスが削除されて、ドロップダウンメニューが消えるようになったと思います。
ブラウザチェックをしてみましょう。
ホバーを外すとドロップダウンメニューが消えるようになりました。
メニューを新たに3つから4つに変更しても同じように機能するので、試してみてください。あと、index.jsのconsole.logは取得状況を確認したら削除しておきます。
これでドロップダウンの機能的にはほぼ完成なんですが、アニメーションを入れてスムーズにドロップダウンメニューを出していきましょう。
アコーディオンのような動きはCSSのみで実装可能
ドロップダウンが出てくる時に、アコーディオンのように広がるようにアニメーションさせてみましょう。
この実装はCSSのみで可能です。
styles.cssファイルを変更していきましょう。
下記CSSのように、現在のCSSを変更してください。
*, *::after, *::before {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
a {
color: #333;
text-decoration: none;
}
ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
padding: 0;
margin: 0;
}
main {
width: 100%;
margin: 0px auto 10px;
background-color: lightcyan;
}
.menu {
display: flex;
justify-content: center;
}
.menu__link {
display: block;
padding: 10px 20px;
}
.menu__link:hover {
background-color: #ebebeb;
color: #666;
}
.drop-menu {
background-color: #ebebeb; /* 追記 */
position: absolute;
top: 43px;
transition: all .3s;
}
.drop-menu__link {
display: block; /* display: block;に変更 */
background-color: #ebebeb;
padding: 0px 20px; /* 5pxを0pxに変更 */
line-height: 0; /* 追記 */
transform: scaleY(0); /* 追記 */
transition: all .3s; /* 追記 */
}
.drop-menu__link:hover {
background-color: lightcyan;
}
/* ドロップダウン出現後のスタイル */
.drop-menu__link.is-active {
/* display: blockを削除 */
padding: 5px 20px; /* 追記 */
line-height: 1.6; /* 追記 */
transform: scaleY(1); /* 追記 */
}
このCSSはどのように変更しているかというと、
変更前は、display:none;からdisplay: block;に変更することで要素を出したり消したりしてましたが、それをやめて、子メニューのaタグ要素の高さを出すCSSをすべて0にして、ドロップダウンメニューを初期状態で消しています。
padding、line-height、transform: scaleYですべての高さを0にしてます。そして、is-activeを付加することですべてのCSSの高さを戻します。
そうすると、アコーディオンのようにアニメーションが可能になります。
ブラウザチェックしてみましょう。
いい感じでアコーディオンのようなアニメーションになってますね。
このCSSを利用すれば、アコーディオンメニューも再現可能です。
これで、ドロップダウンメニューが完了しました。
今回の内容でわからない部分があれば、完成のデータをこちらからダウンロードできます。
今回はこれで以上になります。
もし実装などで上手くいかないことありましたら、下記ボタンからご相談いただけましたらありがたいです。