はじめに
Webサイトによく出てくるサイドバーを作りたいと思います。
これまで作ってきたサイドバーを共通化して、複数のHTMLで利用可能なようにします。
また、ハンバーガーアイコンはこれまではdiv
要素で作成してきましたが、今回はSVGを使用したいと思います。
SVGは自作のもので、第2回 SVG ハンバーガーアイコン+hoverで作ったものを利用しようと思います。
今回実施する内容
第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)で作成したモーダルサイドバーと同じですが、サイドバーに表示する「ホーム」と「マイページ」をリンクをクリックすることで、ページ移動できるようにします。
サイドバーは、第19回では、HTMLのbody
要素内に直接記載していましたが、この部分をJavaScriptに置き換えて作成します。
ソースコード(Git Hub)
環境
OS: Windows 11 JP (64bit)
Microsoft Edge: バージョン 128.0.2739.42 (公式ビルド) (64 ビット)
参考
第2回 SVG ハンバーガーアイコン+hover
JavaScript 第1回 Hello World!と環境構築
第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)
用語
特になし
モーダルサイドバーの共通化の実現方法
サイドバーは、色々なページに移動するためのメニューを表示します。したがって、色々なページで同じサイドバーが必要です。
それを毎回それぞれのHTMLにソースを記載するのは色んな点で思わしくなく、どこかに1つのソースを作成してそれを参照させるほうが普通の考え方だと思います。
どうやって実現するか?なのですが、色々調べてみると
- ファイルを読みこんで実装。PHP使用
- ファイルを読み込んで実装。JavaScript使用
あたりなのかなと思いました。
で、今回は、
- JavaScriptで構築していること
- ローカルPC環境で実施
を踏まえて、ファイルを読み込んで実装。JavaScript使用で実装することにしました。
FileやFileReaderはローカル環境では厳しい
最初は、FileやFileReaderでHTMLを読み込んで、それをinnerHTML
で追加しようと考えました。
しかし、FileやFileReaderを使用するには、input
要素でファイルの設定をしないとダメっぽいんです。
セキュリティ上の制限で、ローカルファイルをHTMLにソース書いただけで選択して読み込んでいくことができないようです。
なので、これは没にして、共通化したJavaScriptを作成しておき、それを読み込むことにしました。
これなら、script
要素でファイル名を指定して読み込めばよいだけなので、大丈夫と思いました。
まずは共通化のHTMLを読み込めるかを確認
以下のファイルを作っていきます。
ファイル名 | 内容 |
---|---|
index.html | 初期のホーム。 |
mypage.html | マイページ。 |
common.js | index.htmlとmypage.htmlから読み込まれるJavaScriptファイルで、単にメニューアイコンという文字列だけを含む。 |
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script src="common.js" defer></script>
</head>
<body>
<div id="commonArea"></div>
<div id="home"><a href="index.html">ホーム</a></div>
<div id="mypage"><a href="mypage.html">マイページ</a></div>
</body>
</html>
mypage.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mypage</title>
<script src="common.js" defer></script>
</head>
<body>
<div id="commonArea"></div>
<div id="home"><a href="index.html">ホーム</a></div>
<div id="mypage"><a href="mypage.html">マイページ</a></div>
</body>
</html>
common.js
const commonArea = document.getElementById("commonArea");
window.addEventListener("DOMContentLoaded", () => {
commonArea.innerHTML ="メニューアイコン";
});
index.html
とmypage.html
の説明
index.html
とmypage.html
はコピーして作成して修正しているだけで、ほとんど同じです。
head
要素内のscript
要素で、common.js
の読み込みを行うようにしています。script
要素内のdefer
については、JavaScriptの記事の初期のJavaScript 第1回 Hello World!と環境構築で記載しましたが、
defer 属性の付いたスクリプトは、スクリプトが読み込まれて評価が完了するまで、DOMContentLoaded イベントの発生が抑制されます。
ということで設定しています。
body
要素の最初では、<div id="commonArea"></div>
を追加して、common.js
で読み込んだHTMLを表示するエリアとして使用します。
common.js
の説明
ほとんど内容はありませんが、index.html
とmypage.html
のcommonArea
に「メニューアイコン」という文字列を表示するようにしています。
window.addEventListener
でDOMContentLoaded
の時に表示するようにしました。
どこでイベントを追加するのがいいのか判断ついておりませんが、思いついたもので設定しました。
上記のような感じで、とてもシンプルですが、common.js
の内容が読み込まれました。
サイドバーを共通化
以下のファイルを作っていきます。
ファイル名 | 内容 |
---|---|
index.html | 初期のホーム。 |
mypage.html | マイページ。 |
sideBar.js | index.htmlとmypage.htmlから読み込まれるJavaScriptファイルで、メニューアイコンとサイドバーを含む。 |
sideBar.css | サイドバー用のCSS。 |
humbergerIcon.svg | ハンバーガーアイコンのSVG |
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<link rel="stylesheet" href="sideBar.css" type="text/css">
<script src="sideBar.js" defer></script>
</head>
<body>
<div id="menuSideBar"></div>
<div class="title">ホーム</div>
</body>
</html>
mypage.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mypage</title>
<link rel="stylesheet" href="sideBar.css" type="text/css">
<script src="sideBar.js" defer></script>
</head>
<body>
<div id="menuSideBar"></div>
<div class="title">マイページ</div>
</body>
</html>
sideBar.js
let offset = -200;
window.addEventListener("DOMContentLoaded", () => {
let strHtml = `\n`;
strHtml += `\t<img src="humbergerIcon.svg" id="menuIcon" class="menuIcon"/>\n`;
strHtml += `\t<div id="sideBar">\n`;
strHtml += `\t\t<img src="humbergerIcon.svg" id="menuIcon2" class="menuIcon"/>\n`;
strHtml += `\t\t<div id="menuList">\n`;
strHtml += `\t\t\t<div id="home" class="menuItem">ホーム</div>\n`;
strHtml += `\t\t\t<div id="mypage" class="menuItem">マイページ</div>\n`;
strHtml += `\t\t</div>\n`;
strHtml += `\t</div>\n`;
strHtml += `\t<div id="cover" class="cover"></div>\n`;
menuSideBar.insertAdjacentHTML("beforeend", strHtml);
document.getElementById("menuIcon").addEventListener("click", () => {
showSideBar();
});
document.getElementById("menuIcon2").addEventListener("click", () => {
showSideBar();
});
document.getElementById("home").addEventListener("click", () => {
showSideBar();
setTimeout(() => {
location.href = "index.html";
}, 300);
});
document.getElementById("mypage").addEventListener("click", () => {
showSideBar();
setTimeout(() => {
location.href = "mypage.html";
}, 300);
});
document.getElementById("cover").addEventListener("click", () => {
showSideBar();
});
});
const showSideBar = () => {
document.getElementById("cover").classList.toggle("coverVisible");
offset = -offset;
const animation = document.getElementById("sideBar").animate(
[
{ transform: `translateX(${offset}px)` }
],
{
duration: 300,
fill: "forwards"
},
);
};
sideBar.css
/* menu Icon用開始 */
.menuIcon {
position: absolute;
top: 2px;
left: 2px;
width: 48px;
height: 48px;
}
.menuIcon:hover {
background-color: #999;
border-radius: 50%;
}
/* menu Icon用終了*/
/* side Bar用開始 */
#sideBar {
position: absolute;
bottom: 0px;
left: -200px;
width: 200px;
height: 100%;
border: 0px solid gray;
z-index: 99;
background-color: #fff;
}
#menuList {
position: absolute;
top: 60px;
}
.cover {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: gray;
visibility: hidden;
opacity: 0.3;
z-index: 98;
}
.coverVisible {
visibility: visible;
}
#main {
position: absolute;
top: 60px;
left: 200px;
font-size: 2em;
}
.menuItem {
width: 180px;
padding-left: 10px;
padding-right: 10px;
padding-top: 10px;
padding-bottom: 10px;
}
.menuItem:hover {
background-color: #ccc;
border-radius: 10% / 50%;
}
.title {
position: absolute;
left: 100px;
}
index.html
とmypage.html
の説明
index.html
とmypage.html
はコピーして作成して修正しているだけで、ほとんど同じです。「まずは共通化のHTMLを読み込めるかを確認」と記載内容もほとんど同じです。
head
要素では、sideBar.css
を読み込むようにしたことや、読み込むJavaScriptのファイル名をcommon.js
からsideBar.js
に変更しました。
body
要素では、最初にサイドバーを設置するdiv
要素を配置していますが、id
をmenuSideBar
としました。
sideBar.js
で読みこんだHTMLを表示するエリアとして使用します。
sideBar.js
の説明
sideBar.js
の内容は、第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)で実施していることと同じです。
let offset= -200;
は、サイドバーを画面の左側にオフセットしておくための設定です。
window.addEventListener("DOMContentLoaded", () => ...});
の部分は、ウィンドウのDOMを読み込んだ時に実施する動作を記載しますが、いくつかのパートに分かれています。
strHtml
には、ハンバーガーアイコンとサイドバーを表示するソースを記載します。ウィンドウの
内容は、第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)を参考ください。\t
や\n
でタブや改行を入れているため、少し見づらいかもしれません。
menuSideBar.insertAdjacentHTML("beforeend", strHtml);
で、上記のHTMLソースを読み込んで、div
要素にくっつけています。
document.getElementById("menuIcon").addEventListener
とdocument.getElementById("menuIcon2").addEventListener
は、サイドバーのハンバーガーアイコンをクリックした時にshowSideBar()
を実行して、サイドバーの表示と非表示を切り替えます。
document.getElementById("home").addEventListener
とdocument.getElementById("mypage").addEventListener
は、サイドバーの「ホーム」と「マイページ」をクリックした時に、showSideBar()
を実行しつつ、setTimeout
でlocation.href
を使用してリンク先に遷移しています。setTimeout
を使用したのは、サイドバーが左側へ移動するための時間を待つためであり、300msを設定しました。
document.getElementById("cover").addEventListener
は、サイドバー表示時に、画面を少しグレーアウトしてカバーをかけるのですが、それをクリックした時に、showSideBar()
を実行し、サイドバーを非表示にします。
上記の5つのaddEventListener
のgetElementById
で指定するIDは、strHtmlに記載したHTML内のIDであり、insertAdjacentHTML
でHTMLを加えた後でなければ、IDが存在しないため、この箇所に記載をしました。
showSideBar
の部分は第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)と同じです。300msでサイドバーの表示と非表示の切り替えます。
sideBar.css
の説明
第19回 JavaScript アニメーションによるモーダルサイドバー(translateX)と内容はほぼ同じです。
サイドバーの見た目を少し良くしたいため、CSSで装飾をしていますが、本質ではないため、割愛します。
サイドバーを共通化の別パターン
「ホーム」や「マイページ」をクリックした時にサイドバーを非表示に切り替えて、リンク先に遷移するのに、setTImeoutを使用していました。
この部分について、@junerさんにコメントをいただき、Promiseを使用した方法を紹介いただきましたので、それを追記します。
thenで実装
showSideBar()
関数はもともとこんな感じでした。
const showSideBar = () => {
document.getElementById("cover").classList.toggle("coverVisible");
offset = -offset;
const animation = document.getElementById("sideBar").animate(
[
{ transform: `translateX(${offset}px)` }
],
{
duration: 300,
fill: "forwards"
},
);
};
これを以下のように最後の部分にreturn animation.finished;
を追加します。
const showSideBar = () => {
document.getElementById("cover").classList.toggle("coverVisible");
offset = -offset;
const animation = document.getElementById("sideBar").animate(
[
{ transform: `translateX(${offset}px)` }
],
{
duration: 300,
fill: "forwards"
},
);
return animation.finished;
};
これを実施することで、animationの実行結果を応答として持つことができます。
Animation:finishedによれば、
Animation.finished は ウェブアニメーション API の読み取り専用プロパティで、アニメーションの再生が完了すると解決される Promise を返します。
ということで、アニメーションの再生完了をPromiseで応答できます。
続いて、以下の「ホーム」や「マイページ」のaddEventListener
の部分を見直します。
document.getElementById("home").addEventListener("click", () => {
showSideBar();
setTimeout(() => {
location.href = "index.html";
}, 300);
});
document.getElementById("home").addEventListener("click", () => {
showSideBar().then(() => {
location.href = "index.html";
});
});
showSideBar
関数にthenを記載することで、return
のPromiseを受けた後、その後にリンク先へ遷移することができます。
async/awaitで実装
次は、async/awaitでの実装です。
showSideBar
関数の修正は、「thenで実装」と同じです。
「ホーム」や「マイページ」のaddEventListener
の部分を見直します。
document.getElementById("home").addEventListener("click", async () => {
await showSideBar();
location.href = "index.html";
});
「ホーム」や「マイページ」のaddEventListener
のところの無名関数をasyncを記載し、showSideBar
関数にawaitを付与すると、showSideBar
のreturn
のPromiseを受けた後、その後にリンク先へ遷移することができます。
おわりに
概ね期待通りのものは作れたかなと思います。
ただし、少しソースコードはもっさりしているので、もう少しすっきりさせたいなという感じがあります。
汎用性を求めるなら、もう少しソースコードはまとめたい感じはします。
例えば、サイドバーに追加するメニューは一つ一つ作成していますが、もう少しすっきりさせるならば、別にファイルを用意してメニューリストを読み込んでHTMLとaddEventListener
を追加できるようにしたほうが、汎用性が上がり、すっきりもしそうです。
ローカル環境ではファイル読み込み課題があるので、その辺はまあいいかなというのと、今回初めて作っているというのもあって、まずはお試しで始めたところもあって、どういう実装がスマートなのかも手探りな感じでした。
おわりに2
@junerさんコメントありがとうございました。
「サイドバーを共通化の別パターン」にコメントいただいたreturn animation.finished
を追加して、then、および、async/awaitで実装する内容と追記しました。
ソースコードもGit Hubに追加しました。
then、および、async/awaitの実装が、自分含め、どなたかの参考になれば幸いです。