この記事では、はてなブログで目次を固定配置し、表示/非表示をボタンで切り替える方法について解説します。目次の固定配置からボタンの作成、デザイン、表示の切り替えまで、ステップバイステップでご紹介します。
また、ページの最後にはこの記事で紹介しているコードをまとめてあります。
目次を固定配置する
目次の構造
はてなブログでは、[:contents]
と書くと記事内の<H>
タグ(見出し)を読み取って、目次を自動生成してくれます。
HTMLで確認すると、だいたい以下のような構造になっています。
<!-- "[:contents]"で生成される目次の構造 --> <ul class="table-of-contents"> <li><a href="#大見出し1">大見出し1</a> <ul> <li><a href="#中見出し1">中見出し1</a> <ul> <li><a href="小見出し1">小見出し1</a></li> </ul> <li><a href="#中見出し2">中見出し2</a></li> </li> </ul> </li> <li><a href="#大見出し2">大見出し2</a> <ul> <li><a href="#中見出し2">中見出し2</a></li> </ul> </li> </ul>
<ul>
タグでリストを構成し、table-of-contents
というクラス名を付与しています。
上のコード で生成される目次はこちらです。
固定配置するコード
このタグとクラス名はいつも変わらないので、こちらを利用してCSSでスタイルを指定すれば、目次の表示をページ内の任意の場所に固定することができます。
以下がそのコードです。
CSS(目次)
ul.table-of-contents { position: fixed; top: 30px; left: 10px; z-index: 100; max-height: calc(100vh - 115px); overflow: auto; /* ココから下はデザイン */ background: rgb(229,242,239,0.9); border-radius: 7px; box-shadow: rgb(0,0,0,0.1)0 0 9px; }
position: fixed
とtop
、left
position: fixed
でページをスクロールしても要素を常に同じ位置に固定し、top
、left
で上端、左端からのFIX位置を指定しています。
z-index: 100
- 記事本編など他の要素と重なったとき、前面に出すために指定しています。数値が大きいほど、優先的に前面で表示されます。たとえば、3つの要素にそれぞれ「10、50、100」と指定すれば、3つが重なったとき「100」が一番前に出て、「50」が真ん中、「10」が最背面にレイアウトされることになります。
max-height: calc(100vh - 115px)
とoverflow: auto
- 目次の最大縦幅を、閲覧しているブラウザの縦幅いっぱいから115px差し引いた値とし、ブラウザの縦幅が目次より狭いときは
overflow: auto
でスクロールバーを表示させ、目次を下まで閲覧できるようにしています。
- 目次の最大縦幅を、閲覧しているブラウザの縦幅いっぱいから115px差し引いた値とし、ブラウザの縦幅が目次より狭いときは
こちらを、「」に貼り付けるか、ページ内に<style>
~</style>
で囲んで設置すると、目次を記事ページの指定した場所に固定配置することができます。
表示/非表示をボタンで切り替え
常に固定されていると、記事を読んでいる方がブラウザ幅を縮めたり、見出しの文字数が多かったりで目次の幅が広いなど、目次を非表示にしたいことがあると思います。
そこで、目次の表示/非表示を切り替えるボタンを設置して、ユーザー(読者)が使いやすいように配慮します。
ボタンを作成
HTMLの<button>
タグでボタンを配置し、CSSで装飾、JavaScriptでボタンをクリックすると目次を出し入れする機能を装備します。
HTML(ボタン)
<button id="toggle-button" class="nomal-style"> <img src="画像URL" alt="代替テキスト" width="18" height="18" loading="lazy" itemprop="image" /> </button>
Output
<button>
タグでボタン要素にして、アイコン画像をボタンに表示することにします。これだけだと、まだ画像だけで寂しいですね。(^◡^)✨
以下コードの解説です。
id="toggle-button"
- ボタン要素をJavaScriptで操作するため、
id
属性を指定しています。id
属性はページ内で一意である必要があり、その要素を効率的に取得できるため、特定の要素をJavaScriptで操作する際にはid
属性を指定することが一般的です。
- ボタン要素をJavaScriptで操作するため、
class="nomal-style"
- ボタンのスタイル(装飾など)をCSSで適用するために指定しています。
src="画像URL"
- ボタンに表示するアイコン画像のURLアドレスを指定します。
alt="代替テキスト"
- 指定したボタンに表示するアイコン画像の代わりとして、画像が表示できない場合やアクセシビリティの向上を図るため、画像の説明を記載します。
具体的には、ユーザーがブラウザの画像表示を無効にしている場合や、ページの読み込み中に画像の取得に問題が発生した場合の状況に備えて提供され、ユーザーにとって情報の喪失を最小限に抑えるのに役立ちます。また視覚障害者にとって、画像の内容や役割を理解するのに重要な属性値です。
- 指定したボタンに表示するアイコン画像の代わりとして、画像が表示できない場合やアクセシビリティの向上を図るため、画像の説明を記載します。
CSS(ボタン①)
/* ボタンの基本スタイル */ button#toggle-button { border: none; border-radius: 10px; padding: 5px 0 4px; font-weight: 600; text-shadow: 0px 0px #fff; background: rgba(126, 170, 212, 0.5); width: 189px; } /* マウスホバー時のスタイル変更 */ button#toggle-button:hover { transform: translateY(-4px); box-shadow: rgba(0, 0, 0, 20%) 0 0 9px; } /* ボタンがアクティブ(クリック)されたときのスタイル */ button#toggle-button:active { box-shadow: rgba(0, 0, 0, 0.15) 0 0; transform: translate(4px); } /* 通常スタイルのボタンに表示されるテキストコンテンツ(after要素) */ button#toggle-button.nomal-style::after { content: 'CONTENTS OPEN'; } /* アクティブスタイルのボタンに表示されるテキストコンテンツ(after要素) */ button#toggle-button.active-style::after { content: 'CONTENTS CLOSE'; } /* 目次が開いているときに変化するボタンスタイル */ button#toggle-button.active-style { background: #5a7b99; color: #fff; text-shadow: 0px 0px #000; } /* ボタン内の画像(目次アイコン)スタイル */ #toggle-button img { margin: 2px 5px -3px 0px; }
Output
CSSで、装飾しました。この記事の左上に出ているボタン自体が、Outputです。(^v^)σ
:hover
- ボタンをマウスオーバーしたとき、少し上にずらして影をつけています。
:active
- ボタンをクリック(アクティブ)した瞬間、影を消して少し右にずらしています。
::after
content
プロパティで「CONTENTS OPEN」と「CONTENTS CLOSE」というテキストを表示しています。
さらに、クリックして目次が開いたときと閉じたときのボタンデザインを変更するために、JavaScriptでクラスを切り替えています。
JavaScript①
var button = document.getElementById("toggle-button"); button.addEventListener("click", function() { // ボタンがクリックされたときの処理 if (button.classList.contains("nomal-style")) { // inactive-style クラスが存在する場合、それを削除して active-style クラスを追加 button.classList.remove("nomal-style"); button.classList.add("active-style"); } else { // active-style クラスが存在する場合、それを削除して nomal-style クラスを追加 button.classList.remove("active-style"); button.classList.add("nomal-style"); } });
このスクリプトは、ボタン要素にクラス名nomal-style
がある場合はそれを消し、active-style
クラスを追加してswap(切り替え)します。また逆に、active-style
クラスが存在するときは、それを削除してクラス名nomal-style
を付与します。
これにより、ボタンに指定したCSSスタイルの適用が変わり、デザインを変化させています。
<script>
~</script>
)で囲み、記事中(最下部を推奨)もしくは「ダッシュボード -> デザイン設定 -> 」などに貼り付けてください。
ボタンを目次の上に配置
CSSでボタンを目次の上に配置します。
CSS(ボタン②)
/* ボタンの基本スタイル */ button#toggle-button { position: fixed; top: 22px; left: 20px; z-index: 100; }
position: fixed
- 目次と同様、ボタンの要素を常に同じ位置に固定します。
top
、left
- 上端、左端からの位置を指定。目次位置との兼ね合いを見て、調整します。
z-index: 100
- 目次同様、他の要素と被ったとき、前面にないとボタンが押せずに目次の表示/非表示切り替えができなくなってしまうのを防ぐため、要素の重なりを前面に出すように指定しています。ちなみに最大値は「2,147,483,647」です笑
これで、作成した表示の切り替えボタンが目次の上に配置されました。次に、先ほどのJavaScriptコードに目次の表示/非表示機能を追加して、実装します。
表示/非表示を切り替えるコード
JavaScript②
const tableOfContents = document.querySelector(".table-of-contents"); const toggleButton = document.getElementById("toggle-button"); // 初期状態で非表示にする tableOfContents.style.left = "-1000px"; toggleButton.addEventListener("click", () => { if (tableOfContents.style.left === "10px" || !tableOfContents.style.left) { tableOfContents.style.left = "calc(-100% - 100px)"; // ボタンクリック時、非表示位置に移動 } else { tableOfContents.style.left = "10px"; // ボタンクリック時、表示位置に移動 } }); tableOfContents.addEventListener("click", (event) => { if (event.target.tagName === "A") { tableOfContents.style.left = "calc(-100% - 100px)"; // 目次クリック時、非表示位置に移動 } });
このJavaScriptコードは、記事ページ上での目次(table of contents)の表示と非表示を制御するものです。以下コードの解説です。
const tableOfContents = document.querySelector(".table-of-contents")
- ウェブページ内からクラス名が "table-of-contents" で指定された要素を取得して、
tableOfContents
という変数に格納します。これは目次を表す要素です。
- ウェブページ内からクラス名が "table-of-contents" で指定された要素を取得して、
const toggleButton = document.getElementById("toggle-button")
- ウェブページ内からIDが "toggle-button" の要素を取得して、
toggleButton
という変数に格納します。この要素は目次の表示/非表示をトグルするボタンを表します。
- ウェブページ内からIDが "toggle-button" の要素を取得して、
tableOfContents.style.left = "calc(-100% - 100px)"
- ページを読み込んだときに目次位置を非表示に設定します。
toggleButton.addEventListener("click", () => { ... });
- "toggle-button" 要素(トグルボタン)がクリックされたときの処理を設定します。クリック時に目次の表示状態をトグルするコードが含まれています。
- もし目次が表示中であれば、非表示に変更し、
- もし目次が非表示であれば、表示に変更します。
- "toggle-button" 要素(トグルボタン)がクリックされたときの処理を設定します。クリック時に目次の表示状態をトグルするコードが含まれています。
tableOfContents.addEventListener("click", (event) => { ... });
- 目次がクリックされたときの処理を設定します。もしクリックされた要素が "A" タグ(リンク)であれば、目次を非表示にすることで、目次が選択されたリンクをクリックしたら自動的に閉じるようになります。
このコードは、ボタンをクリックすることで目次を表示/非表示できるようにします。また、目次内のリンクをクリックすると目次が自動的に非表示になります。
CSS(目次・最終形)
ボタンクリックで目次の表示/非表示を切り替えるため、初期状態では目次は非表示にしておくこととします。また、表示/非表示の切り替え時にスムーズにスクロールするアニメーション効果を追加します。
最終的な目次のスタイルは以下となります。
ul.table-of-contents { position: fixed; top: 30px; left: calc(-100% - 100px); // 初期位置を画面左に隠す z-index: 100; max-height: calc(100vh - 115px); overflow: auto; background: rgb(229, 242, 239, .9); border-radius: 7px; box-shadow: rgb(0, 0, 0, .1)0 0 9px; animation: scrollShift 0.5s; // キーフレーム名と時間を指定 transition: left 0.5s; // スムーズなアニメーション } @keyframes scrollShift { 0% { left: calc(-100% - 100px) } 100% { left: 10px } }
以下は追加・変更した項目です。
left: calc(-100% - 100px)
- ページ読み込み時の初期位置を左に隠しています。-100%というのは「要素幅(目次の幅)100%分、左(left)にマイナス」という意味です。
calc
は式の関数で、目次幅分左に移動させ、さらに余分に100px左に移動(-100px
)させた合計値を計算しています。
- ページ読み込み時の初期位置を左に隠しています。-100%というのは「要素幅(目次の幅)100%分、左(left)にマイナス」という意味です。
animation: scrollShift 0.5s
- この部分はCSSアニメーションを設定しています。具体的には、「scrollShift」という名前のキーフレームアニメーションを0.5秒(500ミリ秒)の時間で実行するように指定しています。このアニメーションは要素の「left」プロパティの値を変化させ、要素を水平方向にスライドさせる役割を果たします。アニメーションの開始と終了のステップをキーフレームで定義することで、スムーズなアニメーション効果を実現します。
transition: left 0.5s
- 「transition」プロパティを使用して要素の「left」プロパティに変化があった場合に、0.5秒かけてその変化をスムーズに適用することを指定しています。具体的には、「left」プロパティが変更された際に、変更が0.5秒かけて適用され、アニメーションのような効果を生み出します。
@keyframes
- この部分は「scrollShift」という名前のキーフレームアニメーションを定義しています。このキーフレームアニメーションでは、要素の「left」プロパティの値を開始時点(0%)から終了時点(100%)まで変化させるアニメーション効果を設定します。開始時点では「left」プロパティの値を「calc(-100% - 100px)」に設定し、終了時点では「left」プロパティの値を「10px」に設定します。これにより、要素は左からスライドして表示される効果を持つアニメーションが定義されます。
コードのまとめ
※Minify化のため、解説は除いてあります。
コードのminifyミニファイ化 - rubirubi.hateblo.jp
まとめ > CSS
ul.table-of-contents { position: fixed; top: 30px; left: calc(-100% - 100px); z-index: 100; max-height: calc(100vh - 115px); overflow: auto; background: rgb(229, 242, 239, .9); border-radius: 7px; box-shadow: rgb(0, 0, 0, .1)0 0 9px; animation: scrollShift 0.5s; transition: left 0.5s; } @keyframes scrollShift { 0% { left: calc(-100% - 100px) } 100% { left: 10px } } button#toggle-button { border: none; border-radius: 10px; padding: 5px 0 4px; font-weight: 600; text-shadow: 0px 0px #fff; background: rgba(126, 170, 212, 0.5); width: 189px; position: fixed; top: 10px; left: 10px; z-index: 100; } button#toggle-button:hover { transform: translateY(-4px); box-shadow: rgba(0, 0, 0, 20%) 0 0 9px; } button#toggle-button:active { box-shadow: rgba(0, 0, 0, 0.15) 0 0; transform: translate(4px); } button#toggle-button.nomal-style::after { content: 'CONTENTS OPEN'; } button#toggle-button.active-style::after { content: 'CONTENTS CLOSE'; } button#toggle-button.active-style { background: #5a7b99; color: #fff; text-shadow: 0px 0px #000; } #toggle-button img { margin: 2px 5px -3px 0px; }
まとめ > HTML
目次の開閉ボタン
<button id="toggle-button" class="nomal-style"> <img src="好きなアイコン画像URL" alt="代替テキスト" width="18" height="18" loading="lazy" itemprop="image" /> </button>
まとめ > JavaScript
JavaScript①②
<script > var button = document.getElementById("toggle-button"); button.addEventListener("click", function() { if (button.classList.contains("nomal-style")) { button.classList.remove("nomal-style"); button.classList.add("active-style") } else { button.classList.remove("active-style"); button.classList.add("nomal-style") } }); const tableOfContents = document.querySelector(".table-of-contents"); const toggleButton = document.getElementById("toggle-button"); tableOfContents.style.left = "-1000px"; toggleButton.addEventListener("click", () => { if (tableOfContents.style.left === "10px" || !tableOfContents.style.left) { tableOfContents.style.left = "calc(-100% - 100px)"; } else { tableOfContents.style.left = "10px"; } }); tableOfContents.addEventListener("click", (event) => { if (event.target.tagName === "A") { tableOfContents.style.left = "calc(-100% - 100px)"; } }); </script>
設置方法
※"好きなアイコン画像URL"のところは、お好きな画像のアドレスを指定してください。
個別の記事に適用する場合
- 目次を作成(
[:contents]
を配置。記事に<H>
タグがあることが条件) - 「まとめ > CSS」コードを記事冒頭に貼る
- 「まとめ > HTML」コードを記事内の任意の場所(目次
[:contents]
の上など)に貼る - 「まとめ > JavaScript」コードを記事の最下部に貼る
以上で、設置は完了です。
CSSを冒頭に貼る理由
CSSスタイルのコードを<style>
タグで囲んだ場合、通常は記事内の<head>
セクションに配置するのが適切です。この方法は、ページの読み込みパフォーマンスに良い影響を与え、スタイルが適切に適用されます。
CSSスタイルを<head>
内に配置する利点は次のとおりです:
- ページ読み込みの高速化: ブラウザはHTML文書を上から下に読み込みます。スタイルが
<head>
内に配置されコンテンツより前にあることで、ブラウザはページの表示を開始する前にスタイルを読み込むことができ、ページの初期表示が高速化されます。 - キャッシュの効率化: スタイルが共通で使用される場合、ブラウザはキャッシュに保存しやすくなり、他のページで再利用できます。
- レンダリングの安定性: スタイルがコンテンツの上部に配置されるため、ページの読み込み中にコンテンツが再レンダリングされることが減少します。
総じてスタイルは<head>
内に配置することが最適であり推奨される、ということになります。
ただしはてなブログでは、記事ごとに<head>
内へのコード記載はできません。ですので、個別の記事に適用させたいケースでは、記事冒頭が最も良い選択となる、ということになります。
JavaScriptを最下部に貼る理由
先ほどと同じ理屈で、ブラウザはHTML文書を上から下に読み込むので、仮にページ最上部にスクリプトを配置すると、HTML要素がすべて読み込まれないうちにスクリプトが実行されてしまい、対象のHTML要素が見つからない可能性が高くなります。
一方、ページの最下部にスクリプトを配置することにより、HTML要素の読み込みを待つことができます。HTML要素が読み込まれた後にスクリプトが実行されるので、要素を正しく操作できるようになります。
スクリプトの用途によっても異なりますが、今回のコードの場合、HTML要素が読み込まれた後にスクリプトを実行したいので、コードはHTML内の</body>
直前に配置するのがベストです。個別の記事に適用させるケースでは、なるべく記事の下の方に配置するのが理想的、ということになります。
ブログ全体に適用する場合
- 「ダッシュボード -> デザイン設定」から、
- 「まとめ > CSS」コードを「」に貼り付け
- 「まとめ > HTML」コードを「記事上」に貼り付け(ヘッダやフッタだと、トップページやアーカイブページで反応してしまう可能性)
- 「まとめ > JavaScript」コードを「フッタ」に貼り付け
れば、すべてのページにコードが適用されます。
おわりに
はてなブログをより魅力的にカスタマイズし、訪問した読者の方に親しみやすい記事閲覧体験を提供するために、目次の固定配置と表示非表示切り替えボタンの実装方法をご紹介しました。
これを活用することで、記事のナビゲーションがスムーズになり、ユーザーエクスペリエンスを向上させることができます。ぜひ試してみて、あなたのブログをさらに魅力的にしましょう!