表題ではナビゲーションメニューと書いていますが、Webページを作る時に重要な要素をここで紹介します。
パンくずリストは忘れられがちですが、閲覧者がWebサイト内で自分がどこにいるのか知るために重要な情報を提供します。
検索ボックスはあったほうが良いけど、何かあった時に面倒だからGoogle製のボックスを設置しています。
アコーディオン型の見出しは、特に長くなりがちなWebページの場合はあったほうがよいですね。
個人的にはWebページごと切り替わってしまうページリンクは不要と考えています。
最後に、ハンバーガーメニューです。
およそ個人でWebサイトを作ろうという話題になると必ず登場する「メニュー」ですよ。
いろんな形があるけど、タップするとズラズラとリンクが表示されるハンバーガータイプのことを指していると考えて良いです。
ここでは、事後のメンテナンスまで含めてコードを紹介します。
当Webサイト作成者は、例示を目的としてマークアップ及びプログラミング例を提供しており、明示または黙示にかかわらず、いかなる責任も負わないものとします。
このページは、説明されているマークアップ及びプログラミング言語、手順を作成およびデバッグするために使用される各種ツールに読者が精通していることを前提にしています。
このページは、特定の機能を説明するのに役立つ可能性がありますが、当Webサイト作成者がこれらの例を変更した上で、特定の要件を満たすために追加の機能を提供したり、システムを構築したりすることはできません。
加えて、この例の手順に従う場合は、読者の各種ファイルを事前にバックアップすることを推奨いたします。
これは賛否両論なんですが、私は付けるようにしています。
私が閲覧者として他のサイトに行くときもパンくずリストを頼りにするからね。
パンくずリストを作るhtmlコード
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home(グラスタの場所一覧表〜入手範囲別〜)</a></li>
<li class="breadcrumb-item"><a href="site_create.html">個人サイト制作:サイトマップ</a></li>
<li class="breadcrumb-item active" aria-current="page">ナビゲーションメニューをCSS・JavaScriptで用意する</li>
</ol>
</div>
パンくずリストを付けるメリットは、キリがないけどまとめるなら次のようになりますね。
次はパンくずリストのCSSですね。
これは上記のような理由からリストタグを使いますが、表示の仕方をCSSでコントロールする必要があります。
パンくずリストを作るfainpan.css
/* パンくずリスト全体のスタイル */
.breadcrumb {
display: flex; /* フレックスボックスで横並びに配置 */
list-style: none; /* リストのスタイルをなしにする */
padding: 10px 15px; /* 内側の余白を調整 */
margin: 0; /* 外側の余白をなしにする */
background-color: #f8f9fa; /* 背景色を追加 */
border-radius: 5px; /* 角を丸くする */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 影を追加 */
font-size: 0.7em; /* フォントサイズを調整 */
}
/* 各パンくずリストアイテムの間に区切りを追加 */
.breadcrumb-item+.breadcrumb-item::before {
content: ">"; /* 区切り文字を設定 */
padding: 0 10px; /* 区切り文字の左右に余白を追加 */
color: #6c757d; /* 区切り文字の色を設定 */
}
/* パンくずリスト内のリンクのスタイル */
.breadcrumb-item a {
text-decoration: none; /* リンクの下線をなしにする */
color: #1A73E8; /* リンクの文字色を設定 (Google blue) */
transition: color 0.3s ease; /* ホバー時の色変化にアニメーションを追加 */
}
/* リンクをホバーしたときのスタイル */
.breadcrumb-item a:hover {
color: #00A2ED; /* リンクの文字色を変更 (Microsoft blue) */
text-decoration: underline; /* リンクに下線を追加 */
}
/* 現在のページのパンくずリストアイテムのスタイル */
.breadcrumb-item.active {
font-weight: bold; /* 文字を太字にする */
color: #6c757d; /* 文字色を設定 */
}
リストと言うと、箇条書きを連想しますよね?
ここで list-style: none; を使うと、リストアイテムのデフォルトの箇条書きスタイル(通常はディスクや数字のようなマーカー)が取り除かれます。
こういうのリストアイテムとか言ったりするけど、パンくずリストでは箇条書きのマーカーなしで表示するのが良いと思います。
とはいえ、このプロパティはリストアイテムの配置方法には影響しません。
そのままではリストのアイテム自体は箇条書き風の縦並びのままです。
パンくずリストはアイテムを横並びに配置したいので、追加のCSSスタイルを使用する必要があります。
例えば、次のようにフレックスボックスを使ってアイテムを横並びに配置できます。
.breadcrumb {
display: flex; /* フレックスボックスで横並びに配置 */
list-style: none; /* リストのスタイルをなしにする */
padding: 10px 15px;
margin: 0;
background-color: #f8f9fa;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 0.7em;
}
この場合、display: flex; がリストアイテムを横並びに配置する役割を果たします。
次は検索ボックスですね。
開口一番言っちゃうけど、自力実装はお勧めしないかな。
検索システムなんてそうそう簡単に作れるもんじゃないし、作ったら作ったで面倒な要望案件の原因になります。
検索ボックスを整えるfainpan.css
/* 検索コンテナのスタイル */
.search-container {
width: 50%; /* 横幅を50%に設定 */
margin-top: 20px; /* パンくずリストの下に配置 */
margin-left: auto; /* 右側に配置 */
background-color: white; /* 背景色を白に設定 */
border-radius: 10px; /* 四隅を丸める */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 影を追加 */
padding: 5px; /* 上下の内側の余白を縮小 */
}
/* 検索コンポーネントのスタイル */
.gcse-search {
width: 100%; /* 横幅を親要素に合わせる */
}
/* 入力フィールドと検索ボタンのスタイル */
input.gsc-input,
input.gsc-search-button {
width: 100%; /* 入力フィールドとボタンの横幅を100%に設定 */
border-radius: 10px; /* 四隅を丸める */
padding: 5px; /* 上下の内側の余白を縮小 */
}
私はGoogleの検索ボックスをそのまま借りてきて、上記のようにCSSでデザインしているだけです。
ユーザーに入力させるUIを自力実装しないようにしてます。
何かあると対応が大変だし、万が一のことがあってもこういう形ならサイト制作者に直接的な責任がかかりません。
この手のUIはいろんな実装方法がありますが、私の場合は見出しタグに連動して自動的に生成されるようにJavaScriptで組んでいます。
もちろんjQuery等のライブラリを使っても良いしそれが一般的な形でもあるんだけど、まずは簡素なものを書いて設置しておき、様子を見ながらライブラリ等のツールを取り入れていく順番でやってますね。
アコーディオン型の見出しを差し込むhtmlコード
<button id="generate-headings" data-open-text="ナビゲーションメニューをCSS・JavaScriptで用意するの目次を開く ▼"
data-close-text="ナビゲーションメニューをCSS・JavaScriptで用意するの目次を閉じる ▲">ナビゲーションメニューをCSS・JavaScriptで用意するの目次を開く
▼</button>
~ ここにコンテンツ内容 ~
<script src="/script/feinheadline1.js"></script>
このコードで <script src="/script/feinheadline1.js"></script> をページ末尾に持ってくる理由はいくつかあります。
早い話が、変なバグに悩まされないようセオリー通りにやっているだけです。
無理に変化球を投げると動作しないときの原因究明に大変な時間がかかってしまうからね。
見出しについては初めからフル表示されているものもありますよ。
どちらでも良いのですが、私の場合はご覧のとおり長いWebページになりがちなので…
閲覧者の動きがよりスムーズになるよう、工夫する必要があります。
アコーディオン型の見出しを作るfeinheadline.css
@charset "UTF-8";
/* 目次アイテムの基本スタイル */
.toc-item {
color: #1A73E8; /* 文字の色 (Google blue) */
font-family: inherit; /* 親要素のフォントファミリーを継承 */
font-size: 100%; /* フォントサイズを100%に設定 */
margin: 0; /* 外側の余白をなしにする */
padding: 5px 0; /* 上下に5pxのパディング */
line-height: 2; /* 行間を2に設定 */
transition: color 0.3s ease-in-out; /* 色変化のアニメーションを追加 */
border-bottom: 1px solid #ddd; /* 各項目の下に薄いボーダーラインを追加 */
padding-bottom: 5px; /* ボーダーラインと内容の間に余白を追加 */
}
/* 目次アイテムをホバーしたときのスタイル */
.toc-item:hover {
color: #00A2ED; /* 文字の色を変更 (Microsoft blue) */
}
/* インデントのスタイル */
.indent-1 {
text-indent: 1em; /* 半角1字分のインデント */
}
.indent-3 {
text-indent: 3em; /* 半角3字分のインデント */
}
.indent-5 {
text-indent: 4em; /* 半角4字分のインデント */
}
/* 各見出しタグ用のスタイル */
.toc-item-h1 a,
.toc-item-h2 a,
.toc-item-h3 a,
.toc-item-h4 a,
.feinheadline-h5 a,
.toc-item-h6 a {
padding: 5px 10px; /* 内側の余白を追加 */
}
/* 目次生成ボタンのスタイル */
#generate-headings {
background-color: transparent; /* 背景色を透明に設定 */
color: #1A73E8; /* 文字の色 (Google blue) */
font-family: inherit; /* 親要素のフォントファミリーを継承 */
font-size: 120%; /* フォントサイズを120%に設定 */
border-radius: 5px; /* 四隅を丸める */
border: none; /* ボーダーをなしにする */
text-decoration: underline; /* 下線を追加 */
cursor: pointer; /* カーソルをポインターに変更 */
text-align: left; /* テキストを左揃えにする */
background: linear-gradient(to bottom, rgba(173, 216, 230, 0.5), rgba(173, 216, 230, 0)); /* 背景のグラデーションを追加 */
font-weight: bold; /* 文字を太字にする */
transition: background 0.3s ease-in-out, color 0.3s ease-in-out; /* 背景色と文字色の変化にアニメーションを追加 */
}
/* 目次生成ボタンをホバーしたときのスタイル */
#generate-headings:hover {
color: #00A2ED; /* 文字の色を変更 (Microsoft blue) */
background: linear-gradient(to bottom, rgba(173, 216, 230, 0.2), rgba(173, 216, 230, 0)); /* 背景のグラデーションを変更 */
}
/* 目次コンテナのスタイル */
#toc-container {
margin-top: 1.5em; /* 上に余白を追加 */
height: 0; /* 初期状態の高さを0に設定 */
overflow: hidden; /* はみ出たコンテンツを隠す */
transition: height 0.5s ease-out; /* 高さの変化にアニメーションを追加 */
}
/* 目次コンテナが表示されたときのスタイル */
#toc-container.show {
height: auto; /* 高さを自動に設定 */
}
こういうページをご覧になるからはご存じと思いますが、「コンテナ」というのは、ある種の箱やエリアといったものを想像して頂けると分かりやすいかと。
ここではCSSによるアニメーション設定について見ていきます。
transition: height 0.5s ease-out; は、CSSのトランジションプロパティを用いて高さの変化にアニメーションを追加するものです。
要素の高さが変更される際にスムーズなアニメーション効果が適用されます。
このプロパティを詳しく見てみましょう。
このプロパティの効果をまとめると、要素の高さが変更される際に、0.5秒間でスムーズなアニメーションを適用し、アニメーションの終わりをゆっくりとすることで、視覚的に自然な効果を実現します。
細かいようですが、こういうことをやらないとカクカクしたページになっちゃいます。
よく言われるところの「あぁ…個人サイトだね…」と感じにくくできるでしょう。
次はJavaScriptへ移ります。
このCSSと密接に関連しているので、注意して頂ければと思います。
このコードは、ページが読み込まれた後に目次を自動生成し、ボタンをクリックすると目次の表示を切り替える仕組みを作っています。
ここで作られたものが上述のCSSと絡んで、滑らかな見出しが自動的に生成されるようになります。
アコーディオン型の見出しを作るfeinheadline1.js
document.addEventListener("DOMContentLoaded", function () {
let headingsGenerated = false;
document.getElementById("generate-headings").addEventListener("click", function () {
let tocContainer = document.getElementById("toc-container");
let button = document.getElementById("generate-headings");
if (!headingsGenerated) {
if (!tocContainer) {
tocContainer = document.createElement("div");
tocContainer.id = "toc-container";
}
let headings = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
headings.forEach((heading, index) => {
let id = `heading-${index + 1}`;
heading.id = id;
let tocItem = document.createElement("p");
tocItem.classList.add("toc-item");
switch (heading.tagName.toLowerCase()) {
case "h1":
tocItem.classList.add("toc-item-h1");
break;
case "h2":
tocItem.classList.add("toc-item-h2");
break;
case "h3":
tocItem.classList.add("indent-1");
break;
case "h4":
tocItem.classList.add("indent-3");
break;
case "h5":
tocItem.classList.add("indent-5", "feinheadline-h5");
heading.classList.add("feinheadline-h5");
break;
case "h6":
tocItem.classList.add("indent-5");
break;
}
let link = document.createElement("a");
link.href = `#${id}`;
link.textContent = heading.textContent;
tocItem.appendChild(link);
tocContainer.appendChild(tocItem);
});
document.getElementById("generate-headings").insertAdjacentElement('afterend', tocContainer);
headingsGenerated = true;
}
if (tocContainer.classList.contains("show")) {
tocContainer.style.height = "0";
button.textContent = button.getAttribute("data-open-text");
} else {
tocContainer.style.height = tocContainer.scrollHeight + "px";
button.textContent = button.getAttribute("data-close-text");
}
setTimeout(() => {
tocContainer.classList.toggle("show");
}, 10);
});
});
いやいや…厳しいと思います。
1ページ作るたびに新しい見出しが生まれるのですから、それらを全て挿入する作業を人力でなんて、現実味がないでしょう。
ミスの原因にもなりますし、何かしらの形で自動生成されるようにした方が良いかと思います。
ここで紹介しているのは、その一例です。
段階的に説明していきますよ?
まずは日本語で書いてから、各コードを具体的に説明してみます。
Webページが読み込まれます。
このページにあるスクリプトは、ページが完全に読み込まれると動き始めます。
まず、ページが全部読み込まれると、そのスクリプトが動き始めます。
そして、「目次ボタン」がクリックされるのを待っています。
ページの中にはたくさんの見出し(タイトル)があるのですが、これを一つ一つ拾って目次を作っていきます。
ここが大変重要です。
見出しを一つ一つ拾って目次を作る部分がループ処理になっています。
具体的には、forEach というループを使って、ページ内のすべての見出し要素(h1 から h6)を順番に処理しています。
ループ処理を使うことで、ページ内のすべての見出しに対して同じ処理を繰り返し行い、それぞれの見出しに対応する目次の項目を作成しているのです。
こうすることで、手作業ではなく自動的に目次を生成することができ、たとえページ内にたくさんの見出しがあっても正確に目次を作成することができます。
このループ処理のおかげで、目次の生成が効率的かつ正確に行えるわけ。
さて、「目次ボタン」をクリックすると、スクリプトはまず目次を入れる箱を探します。
その箱が見つからなかった場合は、新しく箱を作り出します。
そして、見出しがまだ目次に追加されていないかを確認します。
次に、ページの中にある全ての見出しを探します。
それぞれの見出しには特別なIDを付けて、目次の項目に追加します。
この目次の項目はリンクになっていて、クリックするとその見出しにジャンプできます。
目次が完成したら、目次の箱を「目次ボタン」の後ろに置きます。
これで、目次が再度作られることはありません。
最後に、目次が表示されているかどうかを確認します。
もし表示されていたら、目次を隠してボタンのテキストを「目次を開く」に変更します。
逆に表示されていなかったら、目次を表示してボタンのテキストを「目次を閉じる」に変更します。
これがスクリプトの流れです。
目次を自動的に生成し、クリックで表示・非表示を切り替える動きをしているってことですね!
では、具体的にコードを見てみましょう。
document.addEventListener("DOMContentLoaded", function () {
DOMContentLoaded イベントは、HTMLが完全に読み込まれた後に実行されます。
このイベントリスナー内にすべてのスクリプトが含まれています。
ページが読み込まれると動き始めるということです。
let headingsGenerated = false;
目次が既に生成されたかどうかを追跡するためのフラグ変数です。
目次がまだ作られていないか確認しないといけません。
最初は false になっています。
document.getElementById("generate-headings").addEventListener("click", function () {
ボタンがクリックされたときに実行される関数を定義しています。
こちらはボタンクリックで動き始めるということです。
次の4と5で、既に目次の箱(コンテナ)があるか確認し、無ければ新しい箱を作ります。
let tocContainer = document.getElementById("toc-container");
let button = document.getElementById("generate-headings");
まず、getElementById メソッドを使用して、id 属性が toc-container の要素を取得します。
この要素が目次コンテナです。
また、generate-headings ボタンも同じ方法で取得します。
if (!headingsGenerated) {
if (!tocContainer) {
tocContainer = document.createElement("div");
tocContainer.id = "toc-container";
}
目次コンテナが既に存在するか確認します。
存在しない場合(tocContainer が null
の場合)には、新しい div 要素を作成し、その id を toc-container に設定します。
headingsGenerated フラグが false
の場合にのみ、新しい目次コンテナが作成されるようにしています。
let headings = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
querySelectorAll メソッドを使用して、ページ内の全ての見出し要素(h1 から h6 まで)を取得します。
ページ内の見出し(タイトル)を全部見つける部分ですね。
次の7~9で、各見出しごとに目次の項目を作ります。
その項目にリンクを付けて、クリックするとその見出しに飛べるようにします。
headings.forEach((heading, index) => {
let id = `heading-${index + 1}`;
heading.id = id;
let tocItem = document.createElement("p");
tocItem.classList.add("toc-item");
各見出し要素をループして処理します。
各見出し要素に一意のID(heading-1、heading-2 など)を付与します。
新しい p 要素を作成し、クラス toc-item を追加します。
switch (heading.tagName.toLowerCase()) {
case "h1":
tocItem.classList.add("toc-item-h1");
break;
case "h2":
tocItem.classList.add("toc-item-h2");
break;
case "h3":
tocItem.classList.add("indent-1");
break;
case "h4":
tocItem.classList.add("indent-3");
break;
case "h5":
tocItem.classList.add("indent-5", "feinheadline-h5");
heading.classList.add("feinheadline-h5");
break;
case "h6":
tocItem.classList.add("indent-5");
break;
}
見出しのタグ名に応じて、対応するクラスを目次アイテムに追加します。
let link = document.createElement("a");
link.href = `#${id}`;
link.textContent = heading.textContent;
tocItem.appendChild(link);
tocContainer.appendChild(tocItem);
各見出しへのリンクを作成し、目次アイテム(p 要素)に追加します。
目次アイテムを目次コンテナに追加します。
document.getElementById("generate-headings").insertAdjacentElement('afterend', tocContainer);
headingsGenerated = true;
目次コンテナを生成ボタンの後に挿入し、headingsGenerated フラグを trueに更新します。
これにより、目次が再生成されないようになります。
目次の箱をボタンの後ろに配置し、目次が生成されたことを記録するといったイメージでしょうか。
目次が表示されているかどうかを確認し、表示/非表示を切り替えます。
ボタンのテキストも変更します。
if (tocContainer.classList.contains("show")) {
tocContainer.style.height = "0";
button.textContent = button.getAttribute("data-open-text");
} else {
tocContainer.style.height = tocContainer.scrollHeight + "px";
button.textContent = button.getAttribute("data-close-text");
}
show クラスが目次コンテナに含まれているかを確認します。
含まれている場合は、目次コンテナの高さを 0 に設定し、ボタンのテキストを「目次を開く」に変更します。
含まれていない場合は、目次コンテナの高さを内容に合わせて自動調整し、ボタンのテキストを「目次を閉じる」に変更します。
setTimeout(() => {
tocContainer.classList.toggle("show");
}, 10);
少し遅延させて、目次コンテナに show クラスをトグルで追加または削除します。
この遅延により、アニメーションがスムーズに実行されます。
以上を持ちまして、ページの見出しを収集し、クリックで表示/非表示を切り替えられる目次を生成するスクリプトの説明となります。
まずは全体的なコード同士の関わりについて説明が必要です。
全てのページに「メニューを開く」をいうボタンがありますよね?
その部分はハンバーガーメニューを差し込むhtmlコードです。
それをタップするとJavaScriptが動作します。
結果として何が起きるかというと、別に用意してある menu.html というメニューの中身を記述しているhtmlが、ハンバーガーメニューの入口である「メニューを開く」というボタンに差し込まれるわけです。
そして準備してあるCSSがデザインを担当しています。
まずはハンバーガーメニューを差し込むhtmlコードから。
これは全てのページに設置する必要があります。
<div class="fden-hamburger-menu">
<button class="fden-hamburger-button" id="fden-hamburger-button" onclick="toggleMenu()">☰ メニューを開く
▼</button>
<div class="fden-menu" id="fden-menu">
<div id="fden-menu-content"></div>
</div>
</div>
<script src="../script/menu.js"></script>
htmlコードそのものはそんなに複雑ではありません。
ていうかなるべくシンプルに書くようにしているんだけどね。
次は、ハンバーガーメニューの中身が記述されているhtmlファイルです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>fein's personal siteのハンバーガーメニュー</title>
</head>
<body>
<a href="/" style="color:#1A73E8;">ホーム(アナザーエデン グラスタの入手場所一覧)</a>
<a href="/another-eden/anaden_sitemap.html" style="color:#1A73E8;">アナザーエデン関連ページ・サイトマップ</a>
<a href="/another-eden/lua.html" style="color:#1A73E8;">アナザーエデンのプログラミングを個人サイトで試す</a>
<a href="/contents/form.html#star" style="color:#1A73E8;">🌟宿星の断片ドロップ調査</a>
<a href="/contents/form.html#dreamstar" style="color:#1A73E8;">🌟夢見と星跡の計算表</a>
<a href="/contents/site_create.html" style="color:#1A73E8;">個人サイトのホスティングとコンテンツ作成</a>
<a href="/contents/color.html" style="color:#1A73E8;">ウェブセーフカラーパレットなど便利ツール</a>
<a href="/another-eden/atelier_studio.html" style="color:#1A73E8;">ファンサイト制作のためのアトリエ</a>
<a href="/contents/protect.html" style="color:#1A73E8;">X(旧:Twitter)のAIからイラストなどを守る試み</a>
<div id="denmenu">
<a href="#" class="fden-submenu-toggle1" id="toggle-site-content"
onclick="toggleSubMenu('fden-submenu2', 'toggle-site-content')">上記以外のコンテンツも見る▼</a>
<div class="fden-submenu" id="fden-submenu2">
<a href="/another-eden/anaden_beginner.html" style="color:#1A73E8;">アナザーエデンの初心者向けガイド</a>
<a href="/another-eden/anaden_story.html" style="color:#1A73E8;">アナザーエデンのコンテンツリスト</a>
<a href="/another-eden/anaden_boss.html" style="color:#1A73E8;">アナザーエデンの強敵リスト</a>
<a href="/another-eden/anaden_weapon.html" style="color:#1A73E8;">アナデンで揃えたい武器やバッジなど</a>
<a href="/another-eden/another-eden_feinpdf.html" style="color:#1A73E8;">アナザーエデン:feinが書いたレポート集</a>
<a href="/contents/x_exoduslink.html" style="color:#1A73E8;">個人サイトのリンク掲載をご希望の方へ</a>
</div>
<a href="#" class="fden-submenu-toggle2" id="toggle-popular-pages"
onclick="toggleSubMenu('fden-submenu1', 'toggle-popular-pages')">ポータルサイトで人気のページを見る▼</a>
<div class="fden-submenu" id="fden-submenu1">
<a href="https://portal.feinatelier.org/"
style="display: flex; align-items: center; text-decoration: none; color: #00A2ED;">
<img src="/image/anafish.png" alt="アナザーエデンのバディ_ピスケ" style="width: 8%; height: auto; margin-right: 10px;">
fein's portal(ポータルサイト)へ戻ります
</a>
<a href="https://tinyurl.com/2xpfx4ws" style="color:#00A2ED;">グラスタの場所一覧表~入手範囲別~</a>
<a href="https://tinyurl.com/279lmdze" style="color:#00A2ED;">SNSによるアナデンのソシャゲ化を避けるコツ</a>
<a href="https://tinyurl.com/2aoxa9zd" style="color:#00A2ED;">feinのアナデン戦闘史_vol1</a>
<a href="https://tinyurl.com/255tplla" style="color:#00A2ED;">feinのアナデン戦闘史_vol2</a>
<a href="https://tinyurl.com/2bc29bx7" style="color:#00A2ED;">feinのアナデン戦闘史_vol3</a>
</div>
<a href="https://tinyurl.com/2ay3bltd" style="color:#00A2ED;">サイト制作者について</a>
</div>
</body>
</html>
単なるリンクのリストですよ。
コードもいたってシンプル。
これがJavaScriptによって全てのページに差し込まれ、CSSによってデザインされる。
そうするとハンバーガーメニューになるわけです。
では、CSSの説明に移ります。
細々したデザインはそうでもないんだけど、ここの特徴はアニメーション設定になるでしょうか。
閲覧者が頻繁にタップするポイントなので、カクカクした動きにならないようにしています。
/* ハンバーガーメニューのコンテナスタイル */
.fden-hamburger-menu {
position: relative; /* 相対位置を指定 */
}
/* ハンバーガーボタンのスタイル */
.fden-hamburger-button {
font-size: 16px; /* フォントサイズを設定 */
cursor: pointer; /* カーソルをポインターに変更 */
color: #1A73E8; /* ボタンの文字色を設定 (Google blue) */
background: linear-gradient(to bottom, rgba(173, 216, 230, 0.5), rgba(173, 216, 230, 0)); /* 背景のグラデーションを追加 */
border: none; /* ボーダーをなしにする */
padding: 10px; /* 内側の余白を設定 */
border-radius: 5px; /* 四隅を丸める */
font-weight: bold; /* 文字を太字にする */
transition: background 0.3s ease-in-out, color 0.3s ease-in-out; /* 背景色と文字色の変化にアニメーションを追加 */
}
/* ハンバーガーボタンをホバーしたときのスタイル */
.fden-hamburger-button:hover {
background: linear-gradient(to bottom, rgba(173, 216, 230, 0.2), rgba(173, 216, 230, 0)); /* 背景のグラデーションを変更 */
}
/* メニューのスタイル */
.fden-menu {
display: none; /* 初期状態で非表示 */
position: absolute; /* 絶対位置を指定 */
top: 40px; /* 上から40pxの位置に配置 */
left: 0; /* 左端に配置 */
background-color: white; /* 背景色を白に設定 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 影を追加 */
z-index: 1000; /* 重なり順を設定 */
opacity: 0; /* 初期状態で透明 */
transition: opacity 0.5s ease, transform 0.5s ease; /* 透明度と変形にアニメーションを追加 */
transform: translateY(-10px); /* 初期状態で少し上に移動 */
}
/* メニューが表示されたときのスタイル */
.fden-menu.show {
display: block; /* 表示 */
opacity: 1; /* 不透明に設定 */
transform: translateY(0); /* 元の位置に移動 */
}
/* メニュー内のリンクのスタイル */
.fden-menu a {
display: block; /* ブロック要素として表示 */
padding: 15px; /* 内側の余白を設定 */
text-decoration: none; /* 下線をなしにする */
color: black; /* 文字色を黒に設定 */
transition: background 0.3s ease-in-out; /* 背景色の変化にアニメーションを追加 */
}
/* メニュー内のリンクをホバーしたときのスタイル */
.fden-menu a:hover {
background-color: #e0e0e0; /* 背景色を変更 */
}
/* サブメニューのスタイル */
.fden-submenu {
display: none; /* 初期状態で非表示 */
padding-left: 20px; /* 左に余白を設定 */
opacity: 0; /* 初期状態で透明 */
transition: opacity 0.5s ease, transform 0.5s ease; /* 透明度と変形にアニメーションを追加 */
transform: translateY(-10px); /* 初期状態で少し上に移動 */
}
/* サブメニューが表示されたときのスタイル */
.fden-submenu.show {
display: block; /* 表示 */
opacity: 1; /* 不透明に設定 */
transform: translateY(0); /* 元の位置に移動 */
}
/* サブメニューのトグルボタン1のスタイル */
.fden-menu .fden-submenu-toggle1 {
font-weight: bold; /* 文字を太字にする */
color: #1A73E8; /* 文字色を設定 (Google blue) */
cursor: pointer; /* カーソルをポインターに変更 */
}
/* サブメニューのトグルボタン2のスタイル */
.fden-menu .fden-submenu-toggle2 {
font-weight: bold; /* 文字を太字にする */
color: #00A2ED; /* 文字色を設定 (Microsoft blue) */
cursor: pointer; /* カーソルをポインターに変更 */
}
/* ビューポートの幅が768px以下の場合のスタイル */
@media (max-width: 768px) {
.fden-hamburger-button {
font-size: 14px; /* フォントサイズを14pxに設定 */
padding: 8px; /* 内側の余白を縮小 */
}
.fden-menu a {
padding: 10px; /* 内側の余白を縮小 */
}
}
このコードにはいくつかのアニメーションが含まれていますが、主にtransitionプロパティを使用しています。
ここはちょっと細かく分かれているんですよね。
重要なポイントでありながら、あまり大仰なアニメーションは付けたくないです。
今のところ、次のような設定で落ち着いています。
transition: background 0.3s ease-in-out, color 0.3s ease-in-out; /* 背景色と文字色の変化にアニメーションを追加 */
初期状態
opacity: 0; /* 初期状態で透明 */
transition: opacity 0.5s ease, transform 0.5s ease; /* 透明度と変形にアニメーションを追加 */
transform: translateY(-10px); /* 初期状態で少し上に移動 */
表示状態
.fden-menu.show {
display: block; /* 表示 */
opacity: 1; /* 不透明に設定 */
transform: translateY(0); /* 元の位置に移動 */
}
transition: background 0.3s ease-in-out; /* 背景色の変化にアニメーションを追加 */
初期状態
opacity: 0; /* 初期状態で透明 */
transition: opacity 0.5s ease, transform 0.5s ease; /* 透明度と変形にアニメーションを追加 */
transform: translateY(-10px); /* 初期状態で少し上に移動 */
表示状態
.fden-submenu.show {
display: block; /* 表示 */
opacity: 1; /* 不透明に設定 */
transform: translateY(0); /* 元の位置に移動 */
}
全体的に、もう少し遅いほうが良いのかもしれない。
あと、このハンバーガーメニューはワンタッチで移動できるサブメニューのような役割を果たしています。
ページ移動のメインになるのはグローバルナビゲーションとサイトマップ。
だからあまり多くのリンクを設定しないようにしていますね。
上述したけども、個人でサイトを作ろうとしたときに必ずと言って良いほど話題になるのが、このサイトにもある「メニュー」です。
CSSだけで組むこともできますが、素直にJavaScriptを交えたほうがラクかと思いますね。
このサイトではグローバルナビゲーションをメインとして、このハンバーガーメニューはサブメニューとして活躍してもらってます。
function toggleMenu() {
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
if (menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
} else {
menu.style.display = 'block';
button.innerHTML = '× メニューを閉じる ▲';
setTimeout(function () {
menu.classList.add('show');
}, 10);
}
}
function toggleSubMenu(id, buttonId) {
var submenu = document.getElementById(id);
var button = document.getElementById(buttonId);
if (submenu.classList.contains('show')) {
submenu.classList.remove('show');
button.innerHTML = button.innerHTML.replace('▲', '▼');
setTimeout(function () {
submenu.style.display = 'none';
}, 300);
} else {
submenu.style.display = 'block';
button.innerHTML = button.innerHTML.replace('▼', '▲');
setTimeout(function () {
submenu.classList.add('show');
}, 10);
}
}
document.addEventListener('DOMContentLoaded', function () {
fetch('../menu/menu.html')
.then(response => response.text())
.then(data => {
document.getElementById('fden-menu-content').innerHTML = data;
});
document.addEventListener('click', function (event) {
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
var isClickInside = menu.contains(event.target) || button.contains(event.target);
if (!isClickInside && menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
}
});
});
さっきも似たようなこと書いたけど。
いやいや…現実味がないと思いますよ?
全てのページで内容を統一しなければいけないのに。
一応Visual Studio Codeは複数ファイルの一括置換ができますが、あまり長いコードを多数のファイルで置換させていると、あるいはどこかでミスが出ていても気付けない可能性があります。
私はグローバルナビゲーションでしか一括置換はやっていないですね。
SEOの観点からもhtmlコードの直接記述であることが望ましいからです。
しかし、閲覧者の利便性を確保するのがハンバーガーメニューの目的なのですから、ここは何かしらの方法でスクリプトによる自動生成をしたほうが良いでしょう。
では、このスクリプトについて詳細な説明を行います。
メニューを開閉する機能
ボタンをクリックすると、メニューが開いたり閉じたりします。
メニューが表示されている場合は、'show' クラスを削除し、300ミリ秒後にメニューを非表示にします。
メニューが非表示の場合は、メニューを表示し、'show' クラスを追加します。ボタンのテキストも適宜変更されます。
サブメニューを開閉する機能
サブメニューの開閉もメインメニューと同様に動作します。
指定されたIDのサブメニューが表示されている場合は、'show' クラスを削除し、300ミリ秒後に非表示にします。
非表示の場合は、サブメニューを表示し、'show' クラスを追加します。ボタンのテキストも適宜変更されます。
ページの読み込み完了後の処理
ページが完全に読み込まれた後、指定されたURLからメニューのHTMLファイルを取得し、その内容をメニューコンテンツに挿入します。
クリックイベントの処理
ページ内でクリックが発生すると、そのクリックがメニューやボタンの内部で発生したかどうかをチェックします。
もしメニューやボタンの外部でクリックが発生し、メニューが表示されている場合は、メニューを閉じます。
function toggleMenu() {
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
if (menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
} else {
menu.style.display = 'block';
button.innerHTML = '× メニューを閉じる ▲';
setTimeout(function () {
menu.classList.add('show');
}, 10);
}
}
1. メニューとボタンの取得
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
menu にはIDが 'fden-menu' の要素を取得します。
button にはIDが 'fden-hamburger-button' の要素を取得します。
2. メニューの表示/非表示を切り替え
if (menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
} else {
menu.style.display = 'block';
button.innerHTML = '× メニューを閉じる ▲';
setTimeout(function () {
menu.classList.add('show');
}, 10);
}
メニューのクラスリストに 'show' が含まれているかをチェックします。
含まれている場合、メニューから 'show' クラスを削除し、ボタンのテキストを変更し、300ミリ秒後にメニューを非表示にします。
含まれていない場合、メニューを表示し、ボタンのテキストを変更し、10ミリ秒後にメニューに 'show' クラスを追加します。
function toggleSubMenu(id, buttonId) {
var submenu = document.getElementById(id);
var button = document.getElementById(buttonId);
if (submenu.classList.contains('show')) {
submenu.classList.remove('show');
button.innerHTML = button.innerHTML.replace('▲', '▼');
setTimeout(function () {
submenu.style.display = 'none';
}, 300);
} else {
submenu.style.display = 'block';
button.innerHTML = button.innerHTML.replace('▼', '▲');
setTimeout(function () {
submenu.classList.add('show');
}, 10);
}
}
1. サブメニューとボタンの取得
var submenu = document.getElementById(id);
var button = document.getElementById(buttonId);
submenu には指定されたIDの要素を取得します。
button には指定されたIDのボタン要素を取得します。
2. サブメニューの表示/非表示を切り替え
if (submenu.classList.contains('show')) {
submenu.classList.remove('show');
button.innerHTML = button.innerHTML.replace('▲', '▼');
setTimeout(function () {
submenu.style.display = 'none';
}, 300);
} else {
submenu.style.display = 'block';
button.innerHTML = button.innerHTML.replace('▼', '▲');
setTimeout(function () {
submenu.classList.add('show');
}, 10);
}
サブメニューのクラスリストに 'show' が含まれているかをチェックします。
含まれている場合、サブメニューから 'show' クラスを削除し、ボタンのテキストを変更し、300ミリ秒後にサブメニューを非表示にします。
含まれていない場合、サブメニューを表示し、ボタンのテキストを変更し、10ミリ秒後にサブメニューに 'show' クラスを追加します。
document.addEventListener('DOMContentLoaded', function () {
fetch('../menu/menu.html')
.then(response => response.text())
.then(data => {
document.getElementById('fden-menu-content').innerHTML = data;
});
document.addEventListener('click', function (event) {
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
var isClickInside = menu.contains(event.target) || button.contains(event.target);
if (!isClickInside && menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
}
});
});
1. DOMContentLoaded イベント
document.addEventListener('DOMContentLoaded', function () {
fetch('../menu/menu.html')
.then(response => response.text())
.then(data => {
document.getElementById('fden-menu-content').innerHTML = data;
});
ページのDOMが完全に読み込まれた後に実行される関数を設定します。
fetch メソッドで ../menu/menu.html ファイルを取得し、その内容を fden-menu-content 要素に挿入します。
2. クリックイベント
document.addEventListener('click', function (event) {
var menu = document.getElementById('fden-menu');
var button = document.getElementById('fden-hamburger-button');
var isClickInside = menu.contains(event.target) || button.contains(event.target);
if (!isClickInside && menu.classList.contains('show')) {
menu.classList.remove('show');
button.innerHTML = '☰ メニューを開く ▼';
setTimeout(function () {
menu.style.display = 'none';
}, 300);
}
});
ドキュメント全体に対してクリックイベントリスナーを追加します。
クリックがメニューまたはボタンの内部でない場合、メニューを閉じる処理を行います。
このハンバーガーメニューでやっているのは、htmlファイルを多数のWebページで共有するというものです。
これをやらないと、メンテナンスに異常に時間がかかるWebサイトになってしまうんですよね。
メニューの中身となるhtmlファイルを別途用意しておいて、それをJavaScriptを用いて各ページに差し込んでいく。
そうすることで、メニューの中身となるhtmlファイルを書き換えれば全てのWebページに中身が反映されます。
特にこういうハンバーガーメニューみたいな階層構造になっているメニューの場合、そのように作り込んでおけば、後がラクでしょ?
このサイトは次のように一連のナビゲーションを設置しています。
そして閲覧者がスクロールしていくと、スクロールトップボタンが出現し、いつでもページトップに戻れるようになっています。
ページの行き来を左右とするなら、ページの読み進めを上下運動として、それぞれ行き止まりにぶつからないようにしているんだよね。
このグローバルナビゲーションにつけた小さなアニメーションは、それを目にすることで大きなリンクテキストを際立たせる効果があります。
document.addEventListener("DOMContentLoaded", function () {
const links = document.querySelectorAll(".animated-link");
links.forEach(link => {
link.classList.add("loaded");
});
});
このサイトではグローバルナビゲーション用の専用UIを実装するのではなく、アンダーラインのアニメーションを添えることで、少し目立たせるに留めています。
グローバルナビゲーションとして稼働させるにはグローバルナビゲーションを構成するにあるCSSが別途必要です。
では、JavaScriptの説明に移ります。
document.addEventListener("DOMContentLoaded", function () { ... });
const links = document.querySelectorAll(".animated-link");
ここで、一つ注意点があります。
上述の NodeList はJavaScriptのデータ構造の一つです。
querySelectorAll メソッドの結果として返されるもので、選択されたDOM要素のリストを保持します。
NodeListは配列に似ていますが、一部の配列メソッドは使用できません。
このように、querySelectorAll メソッドによって返されるオブジェクトは NodeList と呼ばれるデータ構造です。
これで複数の要素に対してループ処理を行うことができます。
ここらへんを書き込もうとすると、かなりの物量になりそうですね…
querySelectorAll メソッドについても、どこかで触れていきたいと思います。
ここで簡単に説明すると、querySelectorAll
メソッドはJavaScriptで使用されるメソッドの一つで、指定されたCSSセレクターに一致するすべての要素を選択するために使います。
ドキュメント内の特定の条件を満たす全ての要素を検索し、その結果を NodeList
というオブジェクトとして返してるんだよね。
links.forEach(link => { link.classList.add("loaded"); });
このスクリプトの役割は、ページのDOMが完全に読み込まれた後、特定のアニメーションを持つリンク要素に "loaded"
クラスを追加することです。
これでグローバルナビゲーションを構成するにあるCSSにて、スタイルやアニメーションを制御できるようになります。
全ページをリスト化したサイトマップも用意していますが、けっこうなページ数があります。
下記の「カテゴリー分けサイトマップ」のほうが使いやすいでしょう。
アナザーエデンの強敵戦やストーリーコンテンツのリスト、お勧めバッジなどを掲載したコーナーです。
期間限定のない普通のRPGですので、初心者でも安心して続けていけるゲームとなっています。
もっとも重要なグラスタについては、場所別に網羅した表があります。
個人でウェブサイトを作るにはどうすればいいか。
HTML・CSS・JavaScriptの書き方はもちろん、無料かつ広告なしでホームページを作る方法を掲載したコーナーです。
Webデザインやレイアウトについても書いてあります。
ゲームとパソコンだけじゃなく、アウトドアも趣味なんです。
このコーナーでは魚釣りの記録とか、魚料理のレシピ、はたまたサイクリングなどなど。
アウトドアに関連するコンテンツが詰め込まれています。