jQuery / Plugin / jquery.toc — 見出しから目次を作り滑らかに移動する
jquery.tocは選択範囲のh1〜h6を集め、リンク付き目次を生成するプラグインです。本文に.toc({ container: "#contents" })を呼ぶと別の領域へ目次を置き、クリックで目的の見出しへ移動します。本文と目次を分離し、見出し構造と再生成方針を決めます。
ソースと文書の準備
jakobbossekのjquery.tocを使い、同名に近い別プラグインと設定を混ぜません。確認したv1.1ソースを特定コミットに固定しました。jquery.toc.jsの生ファイルをHTML横へ同名で保存します。保存するのはGitHub閲覧画面ではなくJavaScript原文です。
ローカルの二ファイルはtoc-demo.htmlとjquery.toc.jsです。完全版jQuery4.0.0をCDNから読み、jQuery、プラグイン、初期化の順にします。必要な効果のないslimは避けます。同じソースをWindows Edgeの4.0.0・3.7.1で検証しました。
選択範囲内の:headerを全て収集します。例では#articleだけを選び、目次・文書タイトル・asideを外に置きます。h2の下に二つのh3を置き、意味ある段階を保ちます。このソースには最大深さオプションがありません。
フォーカスと再生成を含む完全な例
UTF-8のtoc-demo.htmlで保存し開きます。サーバーやビルド不要ですがCDNへネット接続が必要です。見出しは管理された単純テキストです。原本は見出しHTMLを再構成し文字をHTML文字列へ入れるため、任意の入力や見出し内の操作ボタン・リンクまで扱う汎用例ではありません。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Generate a scoped table of contents</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script src="jquery.toc.js"></script>
<style>
html { scroll-behavior: auto; }
body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 20px; }
#contents { border: 1px solid teal; padding: 16px; }
#contents ul { padding-left: 24px; }
#contents a { overflow-wrap: anywhere; }
#article p { min-height: 440px; }
h2:focus, h3:focus { outline: 3px solid teal; }
button { font: inherit; }
footer { min-height: 100vh; }
</style>
</head>
<body>
<h1>A focused table of contents</h1>
<nav id="contents" aria-label="Article contents"><p>Contents will be generated when scripts load.</p></nav>
<button type="button" id="rebuild">Rebuild original example</button>
<p id="status" role="status">Article text is available below.</p>
<main id="article">
<h2 id="existing-overview">Overview</h2><p>First main topic with an existing stable ID.</p>
<h3>Preparation</h3><p>First subtopic.</p>
<h3>Verification</h3><p>Second subtopic.</p>
<h2>Overview</h2><p>A different section with the same displayed title.</p>
</main>
<aside><h2>Outside the article</h2><p>This heading must not enter the contents.</p></aside>
<footer>Space for the final destination.</footer>
<script>
jQuery(function ($) {
if (typeof $.fn.toc !== "function") return;
const $article = $("#article"), $toc = $("#contents"), original = $article.html();
const $scroller = $(document.scrollingElement);
let generation = 0;
function build() {
generation++; $scroller.stop(true, false);
$article.html(original); $toc.empty().off("click.demo");
$article.toc({ container: "#contents", strip: false, stripAfter: 30, scrollSpeed: 400, scrollOffset: 20 });
// Replace this example's generated links' click handlers only.
$toc.find("a").off("click");
$toc.on("click.demo", "a[href^='#hl_']", function (event) {
const anchor = document.getElementById(this.hash.slice(1));
if (!anchor) return;
event.preventDefault();
const run = ++generation, heading = anchor.parentElement;
$scroller.stop(true, false);
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
$scroller.animate({ scrollTop: Math.max(0, $(heading).offset().top - 20) }, reduce ? 0 : 400, function () {
if (run !== generation) return;
heading.tabIndex = -1; heading.focus({ preventScroll: true });
$("#status").text("Arrived: " + heading.textContent);
});
});
$("#status").text("Entries: " + $toc.find("a").length);
}
$("#rebuild").on("click", function () { build(); window.scrollTo(0, 0); });
build();
});
</script>
</body>
</html>
初期結果です。二つのOverviewは別の節です。
Entries: 4
- 四項目と中二項目の入れ子を確認します。Outside the articleは入りません。
- 最後のOverviewで400ms移動し、見出し上に20px残ってフォーカス輪郭が出ます。
- 目次へキーボードでフォーカスしEnterでも確認します。
- 動きの軽減がreduceなら即時です。
- Rebuild original exampleを繰り返し、先頭とEntries: 4に戻り、重複一覧・入れ子アンカーがないことを確認します。
- 移動中に再生成し、旧完了が新状態を上書きしないか確認します。
生成後、この目次のリンクの元クリック処理だけを置換します。補助コードは実スクロール要素一つを動かし、待機列、取消判定、見出しフォーカス、動きの軽減を扱います。プラグイン原本は変更せず、URLハッシュと履歴も変えません。
基本呼出しと設定の実際
原本だけを見る場合、HTML内の初期化scriptタグは残し、その中のコード全体を次へ置換します。依存ファイル・本文・#contentsは保ち、再生成ボタンと状態表示は削除します。原本の移動が動き、追加のフォーカス・動きの軽減・再生成制御はありません。
$("#article").toc({
container: "#contents",
strip: false,
stripAfter: 30,
scrollSpeed: 400,
scrollOffset: 20
});
#articleは収集元、containerは挿入先です。wrapWithは包むHTMLで既定クラスはtoc_containerです。CSSは#contents内へ限定します。
scrollSpeedはミリ秒です。基本呼出しで400と800を同じリンクで比較し戻します。scrollOffsetは内部アンカーの座標から引く値で、正数20は上の余白です。完全例の補助処理は見出しボックス自体を20pxに合わせるため、インラインアンカー基準と数px違うことがあります。
完全例では.toc()の設定だけを変えても補助ハンドラーは変わりません。animateの400か座標から引く20を修正し、動きの軽減用0は維持します。
実際の短縮判定はstripです。既定にshortenもありますがshorten:trueだけではその分岐に入りません。strip:trueはstripAfterまでsubstringして全項目へ...を付け、短い見出しにも付きます。単語数ではなく文字列単位で絵文字などを分割することがあるため、読みやすさにはstrip:falseを優先します。
アンカー・既存id・導入の限界
見出しのidを上書きせず、内部へhl_112などのid付きaを挿入します。existing-overviewは残ります。生成idは文字列でなく領域・順序・段階によるため同名でもこの例では別です。Unicode見出しも収集しますが、追加や並替えでidが変わり得るので永続リンク規則にはしません。
原本は一覧をappendし見出しを再び包むため、繰返すと重複します。完全例は元のHTMLを復元し目次を空にします。元例への復帰なので、後から加えた本文・入力状態・直接イベントは保存しません。実編集画面は現在内容と安定idの別設計が必要です。
既存テーマやWordPressショートコードの目次と同じ本文へ二重初期化しません。この実習とサイト共通の目次は別です。原本のクリック選択は.toc全体にも及ぶので、多数の目次の独立動作を当然と考えません。
出なければローカルファイルのパスと中身、読込順、見出しと挿入先を調べます。ずれは固定メニュー、後から読まれる画像・フォント、文書末尾の余地を確認します。長文項目はCSSで折り返し、目次がなくても本文を読める状態にします。




