今回の記事は、以前作成した以下UIをベースに「よりユーザーフレンドリーな動き」をJavaScriptで付けます。
JavaScriptの.toggle()で画面右下に追従するフローティングメニューと言っても、メニュー要素外をクリックした時にメニューが閉じるだけなのですが、これだけでユーザーの体験が変わるので、是非参考にしてみてください。
.closest()
JavaScriptの .closest()
は、この要素とその親に(文書ルートに向かって)、指定された CSS セレクターに一致するノードが見つかるまで探索するメソッドです。
.closest(selectors)
文字だけだとかなり伝わりづらいメソッドですが、「closest」を直訳すると「最も近い」の意味です。ので、ざっくり言うと親要素を取得するメソッドで、指定した「タグ・属性」を持つ親要素を瞬時に探し出せます。
詳しくは、毎度おなじみ MDN さんが非常にわかりやすいので参考にしてみてください。
参考 Element.closest()mdn web docs_要素外をクリックしたら閉じるフローティングメニューのサンプル
早速サンプルです。
この記事の右下に表示されている丸い「+」のついたUIがフローティングメニューで、スクロールしても追従してくれます。そして、クリックすると、2つのメニューが上に表示され、メニュー外のどこかをクリックすると、表示されたメニューが格納されます。
実際の動きは以下の動画のようになります。
枠外クリックで非表示になるのは、ユーザーライクな挙動です。
実装の手順と方法
コードの解説の前に、この記事のサンプルUIの実装手順と方法について解説していきます。
まずは、以下のHTMLタグを任意の場所に記述します。
ページ下部に固定で配置するUIなので、なるべく下部の方に構文的にはキレイな感じです。
<div id="floatingMenu" class="notshow">
<div class="flObj top">
<p>メニュー1</p>
<a><i class="fas fa-list-ul"></i></a>
</div>
<div class="flObj middle">
<p>メニュー2</p>
<a><i class="fas fa-list-ul"></i></a>
</div>
<div id="flObgToggle" class="flObj bottom">
<a></a>
</div>
</div>
次に、JavaScriptでSwiperのオプションを記述します。
コードは <body>〜</body>
で、</body>
の閉じタグ(クロージングタグ)の前に記述しましょう。
// クリックで処理開始
document.getElementById('flObgToggle').addEventListener('click', () => {
// 親要素のトグルを操作
const flWrapper = document.getElementById('floatingMenu');
flWrapper.classList.toggle('notshow');
});
// 要素外をクリックで非表示
document.addEventListener('click', (e) => {
if(!e.target.closest('div#floatingMenu')) {
console.log("外側をクリックしました。");
const flWrapper = document.getElementById('floatingMenu');
flWrapper.classList.add('notshow');
} else {
console.log("内側をクリックしました。")
}
})
最後にCSSを記述して、見た目を整えれば完成です。
/*親要素 */
#floatingMenu {
position: fixed;
bottom: 90px;
right: 18px;
display: flex;
flex-direction: column;
gap: 18px;
z-index: 2;
}
/* 子要素 */
.flObj {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 15px;
transition: all 0.5s ease;
position: relative;
}
/* 子要素のテキスト */
.flObj p {
margin: 0;
background: #FFF;
padding: 12px 20px 11px;
font-size: 0.9rem;
border-radius: 3px;
position: relative;
filter: drop-shadow(0px 0px 3px #ccc);
}
.flObj p:after {
content: "";
position: absolute;
top: 50%;
left: 100%;
margin-top: -10px;
border: 10px solid transparent;
border-left: 10px solid #FFF;
}
/* 子要素のリンク */
.flObj a {
background: #6bb6ff;
width: 54px;
height: 54px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
box-shadow: 0 0 6px 0 rgb(0 0 0 / 10%), 0 4px 5px 0 rgb(0 0 0 / 16%);
position: relative;
font-size: 1.2rem;
}
.flObj.middle .wpulikefixed {
transition: 0.3s;
}
/* aタグのhover時 */
.flObj.top a:hover, .flObj.middle a:hover {
opacity: 0.8;
}
/* FontAwesomeアイコンのスタイル */
.flObj a i {
color: #FFF;
}
/* +アイコン */
.flObj.bottom a:before {
content: "\f068";
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: "Font Awesome 5 Free" !important;
font-weight: 900;
color: #FFF;
}
/* クリック後の親要素 */
#floatingMenu.notshow {
gap: 0;
}
/* クリック前の各メニュー */
div#floatingMenu.notshow > .flObj.top, div#floatingMenu.notshow > .flObj.middle {
gap: 0;
margin-bottom: -54px;
}
/* クリック前のテキスト */
div#floatingMenu.notshow > .flObj.top p, div#floatingMenu.notshow > .flObj.middle p {
display: none;
}
/* クリック前のメニューの影 */
div#floatingMenu.notshow > .flObj.top a, div#floatingMenu.notshow > .flObj.middle a {
box-shadow: 0 0 2px 0 rgb(0 0 0 / 15%), 0 1px 2px 0 rgb(0 0 0 / 22%);
}
/* クリック前のFontawesomeアイコン */
div#floatingMenu.notshow .flObj.bottom a:before {
content: "\f067";
}
これで完成です!
ざっくりとしたコードの解説
コードは、HTML・JavaScript・CSSの3種類です。
HTMLは「フローティングメニュー」のUIで、JavaScriptは「フローティングメニューの親要素のclass名の操作」がメインです。そして、CSSが「レイアウトと見た目を整える」で、メニューの並びはこのCSSで整えます。
この3つのコードについて、順に解説していきます。
HTML
HTMLは、「floatingMenu」のid名がつく親要素の中に、「flObj」のclass名を持つ要素がメニューで、全部で3つ入れ子で格納されています。
<div id="floatingMenu" class="notshow">
<div class="flObj top">
<p>メニュー1</p>
<a><i class="fas fa-list-ul"></i></a>
</div>
<div class="flObj middle">
<p>メニュー2</p>
<a><i class="fas fa-list-ul"></i></a>
</div>
<div id="flObgToggle" class="flObj bottom">
<a></a>
</div>
</div>
各メニューの要素には、p
タグのテキストと a
タグのリンクでできていて、a
タグにはFontawesomeの i
タグを入れています。
JavaScript
JavaScriptjは、「flObgToggle」のid名を持つボタンをトリガーにして .addEventListener
のクリックで処理を行います。
処理が開始すると、「floatingMenu」のid名がつく親要素のclass名「notshow」をON・OFFのトグルで付け替えします。
// クリックで処理開始
document.getElementById('flObgToggle').addEventListener('click', () => {
// 親要素のトグルを操作
const flWrapper = document.getElementById('floatingMenu');
flWrapper.classList.toggle('notshow');
});
// 要素外をクリックで非表示
document.addEventListener('click', (e) => {
if(!e.target.closest('div#floatingMenu')) {
console.log("外側をクリックしました。");
const flWrapper = document.getElementById('floatingMenu');
flWrapper.classList.add('notshow');
} else {
console.log("内側をクリックしました。")
}
})
そして、要素外をクリックした時のイベントは .addEventListener()
のクリックで発動し、if文の条件分岐で処理内容を分けています。
コードはこれだけで、class名の付け替えを行うトグルに準じて、CSSで見た目の変化を付けていきます。
CSS
CSSは、フローティングメニューのクリック前・後で記述内容が分かれていきます。
各コードはコメントアウトの通りですが、クリック前は親要素「#floatingMenu」に「.notshow」のclass名がついています。
/*親要素 */
#floatingMenu {
position: fixed;
bottom: 90px;
right: 18px;
display: flex;
flex-direction: column;
gap: 18px;
z-index: 2;
}
/* 子要素 */
.flObj {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 15px;
transition: all 0.5s ease;
position: relative;
}
/* 子要素のテキスト */
.flObj p {
margin: 0;
background: #FFF;
padding: 12px 20px 11px;
font-size: 0.9rem;
border-radius: 3px;
position: relative;
filter: drop-shadow(0px 0px 3px #ccc);
}
.flObj p:after {
content: "";
position: absolute;
top: 50%;
left: 100%;
margin-top: -10px;
border: 10px solid transparent;
border-left: 10px solid #FFF;
}
/* 子要素のリンク */
.flObj a {
background: #6bb6ff;
width: 54px;
height: 54px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
box-shadow: 0 0 6px 0 rgb(0 0 0 / 10%), 0 4px 5px 0 rgb(0 0 0 / 16%);
position: relative;
font-size: 1.2rem;
}
.flObj.middle .wpulikefixed {
transition: 0.3s;
}
/* aタグのhover時 */
.flObj.top a:hover, .flObj.middle a:hover {
opacity: 0.8;
}
/* FontAwesomeアイコンのスタイル */
.flObj a i {
color: #FFF;
}
/* +アイコン */
.flObj.bottom a:before {
content: "\f068";
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: "Font Awesome 5 Free" !important;
font-weight: 900;
color: #FFF;
}
/* クリック後の親要素 */
#floatingMenu.notshow {
gap: 0;
}
/* クリック前の各メニュー */
div#floatingMenu.notshow > .flObj.top, div#floatingMenu.notshow > .flObj.middle {
gap: 0;
margin-bottom: -54px;
}
/* クリック前のテキスト */
div#floatingMenu.notshow > .flObj.top p, div#floatingMenu.notshow > .flObj.middle p {
display: none;
}
/* クリック前のメニューの影 */
div#floatingMenu.notshow > .flObj.top a, div#floatingMenu.notshow > .flObj.middle a {
box-shadow: 0 0 2px 0 rgb(0 0 0 / 15%), 0 1px 2px 0 rgb(0 0 0 / 22%);
}
/* クリック前のFontawesomeアイコン */
div#floatingMenu.notshow .flObj.bottom a:before {
content: "\f067";
}
さいごに
今回のUIは、モバイルのようなスクリーンサイズがあまり取れない時、インタラクションな表示・非表示でいろんなメニューを表示できるので、便利です。
使えそうな場合や、フローティングメニューを作る際、是非参考にしてみてください。