はじめに
現在、当ブログは WordPress ではなく Gatsby.js で構築されています 🙇♂️
Qiita や Classmethod などのテック系ブログを見ていると画面サイズが HD 以上になるとサイドバーに現れる「目次」が常々良いなと思っていました。自分でも同じことができないかと思い、WordPress ならプラグインがあるだろうと考えたのですが、案の定良い感じのがない、、、
それなら、自作するしかない。
今回は下記の観点で「目次機能」を自作しました。
- WordPress だけでなく、他の CMS / フレームワークでも扱えるように JavaScript で実装
- 記事中の見出し ( Heading ) を検索して、目次を自動生成
- ページ内リンクを活用することでユーザビリティ向上を目論む
tl; dr
codepen で簡単なサンプルを作っています。ご参考程度に見てもらえればです。
https://codepen.io/canji53/pen/OJyoaRM
環境
- PHP 7.3
- WordPress 5.3
- Gutenberg エディタ
ポイント
ページ内リンクについて
html5 であれば下記のように組めばページ内をジャンプするリンクを作成できます。
詳しくはコチラの SEO ラボさんの記事も参考にしていただければです。
1<a id="title">ジャンプ先</a>
1<a href="#title">ジャンプ元</a>
目次作成の流れ
今回組んだ JavaScript はざっくりと下記のような流れになります。
1. 見出しにサイト内リンクを追加
- 記事内の見出し H2、H3、H4 のそれぞれのレベル要素を取得
- Gutenberg では、見出しブロックが H2、H3、H4 となるため
- 取得した要素ごとに見出し名を id として追加
1const getHeadingElementList = () => {
2 const postElement = document.getElementById("post");
3 return postElement.querySelectorAll(["h2", "h3", "h4"]);
4};
5
6const addInternalLinksForHeading = () => {
7 const headingElementList = getHeadingElementList();
8 Array.prototype.forEach.call(headingElementList, (headingElement) => {
9 headingElement.innerHTML = `<a id="${headingElement.textContent}">` + headingElement.innerHTML + "</a>";
10 });
11};
12
13addInternalLinksForHeading();
2. 見出しのレベルに応じて目次を生成
- 目次を挿入したい要素を予め取得、ここでは contents とする
- 記事内の見出し H2、H3、H4 要素を取得
- 前ステップで取得した要素をループ処理へ
- H2、H3、H4 のレベルに応じて ul li タグの深さが異なる連鎖要素を用意
- #id を a タグで追加することでサイト内リンクを実現
- 前ステップで取得した要素を contents の後列に順次追加
- ループ処理終了
1const getFirstLevelList = (internalLinkId) => {
2 return `
3 <ul>
4 <a href="#${internalLinkId}">
5 <li>
6 ${internalLinkId}
7 </li>
8 </a>
9 </ul>
10 `;
11};
12
13const getSecondLevelList = (internalLinkId) => {
14 return `
15 <ul>
16 <li>
17 <ul>
18 <a href="#${internalLinkId}">
19 <li>
20 ${internalLinkId}
21 </li>
22 </a>
23 </ul>
24 </li>
25 </ul>
26 `;
27};
28
29const getThirdLevelList = (internalLinkId) => {
30 return `
31 <ul>
32 <li>
33 <ul>
34 <li>
35 <ul>
36 <a href="#${internalLinkId}">
37 <li>
38 ${internalLinkId}
39 </li>
40 </a>
41 </ul>
42 </li>
43 </ul>
44 </li>
45 </ul>
46 `;
47};
48
49const addContents = () => {
50 const contentsElement = document.getElementById("contents");
51 const headingElementList = getHeadingElementList();
52
53 Array.prototype.forEach.call(headingElementList, (headingElement) => {
54 const headingAnchorElement = headingElement.getElementsByTagName("a")[0];
55
56 if (headingElement.tagName === "H2") {
57 var contentsLine = getFirstLevelList(headingAnchorElement.id);
58 }
59 if (headingElement.tagName === "H3") {
60 var contentsLine = getSecondLevelList(headingAnchorElement.id);
61 }
62 if (headingElement.tagName === "H4") {
63 var contentsLine = getThirdLevelList(headingAnchorElement.id);
64 }
65
66 contentsElement.insertAdjacentHTML("beforeend", contentsLine);
67 });
68};
69
70addContents();
3. シンプルにスタイル調整
ul li タグのスタイル調整では、主に padding
や margin
周りをいじって「目次感」を再現するのが肝かと思います。
また、サイトデザインにもよるのですが、Qiita 等の技術系のブログではサイドバーに目次が画面追従するように実装されているため、これを position: sticky
で実現するのが尚良しかと思います。
1#contents {
2 position: sticky;
3 top: 25%;
4}
5
6#contents ul {
7 padding-inline-start: 0rem;
8}
9#contents ul li ul,
10#contents ul li ul li ul {
11 padding-inline-start: 0.9rem;
12}
13
14#contents ul li,
15#contents ul li ul li,
16#contents ul li ul li ul li {
17 padding: 0 0.5rem;
18 border-radius: 0.3rem;
19 overflow: scroll;
20}
21
22#contents ul,
23#contents ul li ul,
24#contents ul li ul li ul {
25 margin-bottom: 0;
26 list-style-type: none;
27}
28
29#contents ul a {
30 color: #888;
31 text-decoration: none;
32}
おわりに
わずかなコード量ですが、目次機能を実装するのに意外と時間がかかりました。フロント側の勉強もなお一層必要だなと痛感しました。
また、そこそこ重要なのは CSS 側でのスタイル調整かと思われます。ところが、コチラはお使いの環境によって大きく変わってくるため、時間のかかる部分かもしれないです。当ブログでは、画面幅が 1280px 以上で右側のサイドバーに目次機能が現れますが、基本的には Qiita に近い形で調整しています。