[WordPress]記事のもくじをJavascriptで生成する
                WordPressの記事には、ユーザビリティの観点からもくじがあった方がいいですよね。記事の内容をざっくりと把握できますし、見たい場所まですぐに移動できます。
もくじを作る方法は大体
- 手作業で作る
 - functions.phpでもくじを挿入するフィルターを追加する
 - プラグインを使う
 
このどれかだと思うんですが、色々と思うところがあったのでJavascriptを使ってブラウザに生成させることにしました。
なぜJavascriptなのか
手作業でもくじを作るのは面倒すぎるので論外として、プラグインなしでもくじを作る方法を調べるとfunctions.phpに追記する方法が出てきます。
で、この追記するコードが何やってるかって話なんですけど、記事の全文を正規表現でマッチさせてヘッドライン(h2など)をリストアップ&アンカーポイントのIDをセットし、その結果からリストを生成して記事の前方に挿入するってことを行ってます。おそらくもくじを生成するプラグインも同じようなことをやってるはず。これを記事が読み込まれる度、毎回やってるわけです。
そうなると気になるのがサーバーの負荷ですよね。この程度の処理なんか最近のマシンにかかれば全然余裕でしょうが、塵も積もればってことがあるのでなるべくこういう全文探査するようなものは減らしておきたい。
あと、ブラウザ側なら表示のためにDOMツリーを生成しているので、JavascriptのDOM操作が使えて正規表現使うよりも簡単にできるし。(どちらかというとこっちの理由の方が大きいかも)
そこで、Javascriptを使ってクライアントに生成させようと思ったのです。
コード
window.addEventListener('DOMContentLoaded', function() {
    var contents = document.querySelector(".post-inner"); // 記事の入った要素を取得
    if (!contents) return; // 記事がないなら終了
    var firstH2 = contents.querySelector("h2"); // 最初のh2を取得
    if (!firstH2) return; // h2が存在しないなら終了
    var indexArray = [];
    var src = "<div id='post-index-title'>Contents</div>";
    var headlines = contents.querySelectorAll('h2, h3, h4');
    Array.prototype.slice.call(headlines, 0).forEach(function(headline) {
        var level = Number(headline.tagName.charAt(1)) - 1;
        while (level > indexArray.length) {
            src += "<ul>";
            indexArray.push(0);
        }
        while (level < indexArray.length) {
            src += "</ul>";
            indexArray.pop();
        }
        indexArray[indexArray.length - 1]++;
        if (!headline.id) headline.id = "ContentsIndex_" + indexArray.join("-");
        src += "<li><a href='#" + headline.id + "'>" + headline.innerText + "</a></li>";
    });
    while (0 < indexArray.length) {
        src += "</ul>";
        indexArray.pop();
    }
    var container = document.createElement("div");
    container.id = "post-index-container";
    container.innerHTML = src;
    firstH2.parentNode.insertBefore(container, firstH2);
});このコードをfooter.php内にscriptタグで囲って入れたり、.jsファイルを用意してテーマから読み込むなど、とにかくページが表示された際に実行されるようにしてください。
やってることは難しいこともなく、querySelectorAll('h2, h3, h4')をforEachで回してヘッドラインにIDを指定しながら文字列操作でもくじのhtmlをごりごり生成しているだけ。あとはそれを最初に出現したh2タグの直前に挿入すれば終わりです。
ちなみにforEachあたり複雑な書き方になってるのはIE対策のため。
あと、document.querySelector(".post-inner")の要素内に記事が展開されてること前提で書いてますけど、記事を内包する要素につけるクラス名はテーマによって違ったりするので、ここは自身のテーマに合わせて変えてください。とりあえずTwenty Twentyのテーマをもとにサンプルコード書いてます。
あとはご自由にCSSで装飾してもらえればってところですね。
SEO的にどうなの?
正直、クライアント側のJavascriptで生成する関係上、クローラーにもくじを認識してもらうことは期待していませんでした。その程度別にいいやって思ってたんですよ。
ところが、ちゃんと検索エンジンに認識されて、検索結果にもくじの一部が表示されるようになり、しかもDOMContentLoadedでセットされたIDの要素に対してブラウザはちゃんと移動してくれました。
最近、Javascriptで内容を生成するサイトが増えたからでしょうか。クローラーがJavascriptをちゃんと解釈してくれているあたり、SEO的には問題なさそうに思えます。
問題点は?
Javascriptが無効化されたユーザーにはもくじが表示されない程度です。別に表示されなかったところで致命的な問題になることもないし、Javascriptを使ってないユーザーなんて滅多にいないだろうから気にしなくてもいいと思う。
そもそもの話
記事の内容なんて、自分が編集しない限り時間が経って変わることなんてないのだから、記事が表示される度に毎回生成すること自体が無駄。
記事を保存する時に1度だけ自動で生成してくれればそれで済む話じゃん。
まあ既存の記事全てを一度処理しなきゃいけないって手間があるから、一般ユーザーがプラグインをインストールするだけで簡単に導入できるって手段じゃないけど。
というわけなので、今回紹介した方法は以前まではこのサイトで使用していたものですが、現在はGutenbergを無効化し、自作した独自のエディタに同じようなコードを入れて、記事を書いているブラウザ上でもくじを自動生成するようにしています。
ですが、この方法自体は使える方法だと思うので記事にしました。よければ参考にどうぞ。
コメント