Shopifyのブログ記事内で自動生成される「目次」の実装方法

Shopifyカスタマイズの最新情報は「10ca」にて紹介しております。
【Shopify】ブログ記事内に「目次」を自動生成

「目次」のついているブログ記事をよく見かけます。
Googleの検索結果にも反映されるので、SEOにも効果が高いかと思われます。

Shopifyのブログはシンプルなリッチテキストエディタなので、デフォルトでは力技で目次を作成するしか方法はありません。

調べたところ、WordPressには記事内の見出しタグを解析し、目次を自動生成してくれるプラグインがあるようなので、Shopifyにも似たようなプラグインがあるかと思いきや見当たりません。(調査不足かもしれませんが…)

「アプリがないなら作ればいいじゃない。」

そこで、目次の自動生成を簡単に実装できそうなJavascriptがないか調べたところ、こちらのサイトに辿り着きました。

見出しから目次を自動で作成
JavaScriptで見出しから目次を自動で作成するプログラムの作り方をご紹介します。設置も簡単ですよ。

CoocBook

こちらの記事を参考にShopifyに目次自動生成を実装する方法をご紹介していきます。

今回も「Dawn」をベースに実装していきますが、JSメインなのでテーマ構造がわかる方なら、他テーマでも実装可能だと思います。

1. toc.jsを作成

まずは「新しいassetを追加する」より、「toc.js」を作成します。

ちなみに「TOC」というのは「目次」を英語で「Table of contents」というからですね。

作成した「toc.js」に上記サイトに書かれている「目次作成プログラム」ソースをコピペし、Shopify実装用にハイライト部分(7-8行目)を編集&追記します。

{
    const TOC_INSERT_SELECTOR = '#toc';
    const HEADING_SELECTOR = 'h1,h2,h3,h4,h5,h6';
    const LINK_CLASS_NAME = 'tocLink';
    const ID_NAME = 'heading';
    const tocInsertElement = document.querySelector(TOC_INSERT_SELECTOR);
  	const tocTarget = document.querySelector('#toc_target');
    const headingElements = tocTarget.querySelectorAll(HEADING_SELECTOR);
    const layer = [];
    let id = 0;
    const uid   = () =>`${ID_NAME}${id++}`;
    let oldRank = -1;
    try {
        const createLink = (el) => {
            let li = document.createElement('li');
            let a  = document.createElement('a');
            el.id  = el.id || uid();
            a.href = `#${el.id}`;
            a.innerText = el.innerText;
            a.className = LINK_CLASS_NAME;
            li.appendChild(a);
            return li;
        };
        const findParentElement = (layer, rank, diff) => {
            do {
                rank += diff;
                if (layer[rank]) return layer[rank];
            } while (0 < rank && rank < 7);
            return false;
        };
        const appendToc = (el, toc) => {
            el.appendChild(toc.cloneNode(true));
        };
        headingElements.forEach( (el) => {
            let rank   = Number(el.tagName.substring(1));
            let parent = findParentElement(layer, rank, -1);
            if (oldRank > rank) layer.length = rank + 1;
            if (!layer[rank]) {
                layer[rank] = document.createElement('ol');
                if (parent.lastChild) parent.lastChild.appendChild(layer[rank]);
            }
            layer[rank].appendChild(createLink(el));
            oldRank = rank;
        });
        if (layer.length) appendToc(tocInsertElement, findParentElement(layer, 0, 1));
    } catch (e) {
        //error 
    }
}

元のJSのままでも良いのですが、ページ全体から<h1>タグを探す仕様になっており、目次内にタイトルも入ってしまいますので、記事のエリアを限定しそのエリア内から見出しタグを探す仕様に編集しました。

編集部分を順に説明しますと、7行目でエリアのIDを指定し、8行目でそのエリア内にある見出しタグを探す仕様となっております。

デフォルトのJS仕様については上記リンク先を読んで下さい。

2. toc.cssを作成

同じく「新しいassetを追加する」より「toc.css」を作成し、以下の様にCSSを記述します。

html {
	scroll-behavior: smooth;
}
#toc {
	padding: 1.5em;
	margin-bottom: 1em;
	border: 1px solid #ccc;
}
#toc ol {
	list-style: none;
	counter-reset: toc;
	padding-left: 2em;
	margin: 0;
}
#toc ol li::before {
	counter-increment: toc;
	content: counters(toc, "-");
	display: inline;
	padding-right: .5em;
	margin-left: -2em;
}

今回はシンプルに必要最低限のCSSで出力しますので、サイトに合わせてお好みでデザインして下さい。

一番上のscroll-behavior: smooth;はページ内をビヨ~ンと移動するCSSなので、すでにJSなどで実装している方はなくても構いません。

3. main-article.liquid を編集

sections内のブログ記事を出力するファイルを開きます。
「Dawn」の場合は、main-article.liquidがそれに当たりますが、他テーマの場合は別のファイル名の場合が多いと思うので、「article」って書いてあるファイルを探してみて下さい。

{% schema %}の直前くらいに、1で作成した「toc.js」を読み込む為のliquidタグを挿入します。

{{ 'toc.js' | asset_url | script_tag }}
{% schema %}

続いて同ファイル内にある{{ article.content }}直前に、2で作成したCSSを出力するタグと、記事を出力するタグの<div id="toc"></div>を挿入し、{{ article.content }}を囲っている<div>タグにid="toc_target"を追記すれば、記事直前に目次が出力されるようになります。

<div id="toc_target" class="article-template__content page-width page-width--narrow rte" itemprop="articleBody" {{ block.shopify_attributes }}>
	{{ 'toc.css' | asset_url | stylesheet_tag }}
	<div id="toc"></div>
	{{ article.content }}
</div>

実装が完了したら、見出し構造に気をつけてブログに記事を書いてみましょう。

Ex. その他の拡張

上記の3ステップで実装は可能ですが、3のファイル内の{% schema %}にセクション設定を追記することで、カスタマイザーで制御可能な目次も可能です。

例えばこんな感じに…

{
  "type": "checkbox",
  "id": "show_toc",
  "label": "目次を表示",
  "default": false
},
{
  "type": "text",
  "id": "toc_label",
  "label": "ラベル",
  "default": "目次"
},
{
  "type": "color",
  "id": "toc_bg_color",
  "label": "背景色",
  "default": "#ffffff"
},
{
  "type": "color",
  "id": "toc_border_color",
  "label": "枠色",
  "default": "#cccccc"
}

上から順に説明しますと、目次の表示/非表示切り替えのチェック、「目次」ラベルの編集、背景色の編集、枠色の編集です。

schemaを追記したらliquidも以下の様に編集しましょう。

<div id="toc_target" class="article-template__content page-width page-width--narrow rte" itemprop="articleBody" {{ block.shopify_attributes }}>
	{%- if section.settings.show_toc -%}
		{{ 'toc.css' | asset_url | stylesheet_tag }}
		<div id="toc" style="background-color: {{ section.settings.toc_bg_color }}; border-color: {{ section.settings.toc_border_color }}">
			<p class="toc_label">{{ section.settings.toc_label }}</p>
		</div>
	{%- endif -%}
	{{ article.content }}
</div>
{%- if section.settings.show_toc %}{{ 'toc.js' | asset_url | script_tag }}{% endif -%}

これでカスタマイザーにて「目次を表示」にチェックすれば目次が出力されるようになり、ラベルの編集やカラーの調整も可能となります。

以上、Shopifyのブログ記事内で自動生成される「目次」の実装方法でした〜

3 件のコメント

  1. 「目次」の実装方法についての記事、とても参考になりました。
    ありがとうございます。

    目次をh2前に持ってくる方法はありますでしょうか?
    導入文の後に目次を持ってきたいと思っております。

    お手数ですが、ご検討のほどよろしくお願いいたします。

    1. 「h2前」というのはどういう意味でしょうか?
      記事後に挿入したいのであれば、<div id="toc"></div>{{ article.content }}の後に挿入すれば良いです。
      文中に挿入したいのであれば、記事内の該当箇所に<div id="toc"></div>を挿入すれば出力されます。

      1. 文中に挿入で、希望通りの場所に出力できました。
        ご教示いただき、誠にありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です