Skip to content
Copied!
published on 2026-09-11

4. スクロール制御とルート遷移

サイトナビゲーションを常時表示すると,長い週次記事で本文に利用できる表示領域が狭くなります。そのため,Real Terms では下方向のスクロール時にサイトナビゲーションを非表示にし,上方向のスクロール時とページ先頭付近で再表示します。この挙動は,VitePress で構築した Significant Bit と共通です。

表示状態を表すクラス

表示位置は CSS で制御します。.eg-site-navposition: fixed で配置し,transform の変化に 0.3 秒のトランジションを設定します。

css
.eg-site-nav.scrolled-down {
  transform: translateY(-100%);
  pointer-events: none;
}

.eg-site-nav.scrolled-up,
.eg-site-nav.is-open {
  transform: translateY(0);
}

.scrolled-down はサイトナビゲーションを上方向へ移動し,pointer-events: none によって非表示の要素がポインター操作を受け取ることを防ぎます。メニュー展開中は .is-opentranslateY(0) を適用するため,全画面パネルを表示したままヘッダーだけが画面外へ移動する状態は生じません。

利用者が prefers-reduced-motion: reduce を指定している場合は,トランジションを無効にします。

setupSiteNavScrollReveal

スクロール方向の判定は,app.jssetupSiteNavScrollReveal() が担います。この関数は,siteChromeready フックから一度だけ呼び出します。

js
function sync() {
  var nav = document.querySelector(".eg-site-nav");
  if (!nav) {
    return;
  }
  var y = window.scrollY;
  if (nav.classList.contains("is-open") || y <= nearTopPx(nav)) {
    nav.classList.remove("scrolled-down");
    nav.classList.add("scrolled-up");
    lastY = y;
    return;
  }
  if (y > lastY) {
    nav.classList.add("scrolled-down");
    nav.classList.remove("scrolled-up");
  } else if (y < lastY) {
    nav.classList.remove("scrolled-down");
    nav.classList.add("scrolled-up");
  }
  lastY = y;
}

判定条件は次のとおりです。

  • 先頭付近
    nearTopPx()max(8, nav.offsetHeight) を返します。高さが 64px の場合,ページ先頭から約 64px の範囲ではサイトナビゲーションを表示します。
  • 下方向
    現在位置 y が直前の位置 lastY より大きい場合は,.scrolled-down を付けます。
  • 上方向
    ylastY より小さい場合は,.scrolled-up を付けます。位置が変化しない場合は,現在の表示状態を維持します。
  • メニュー展開時
    .is-open が付いている場合は,スクロール位置にかかわらず表示します。setSiteNavOpen() もメニューを展開するときに .scrolled-down を外し,.scrolled-up を付けます。

scroll イベントのリスナーには { passive: true } を指定し,この処理がスクロールを抑止しないことをブラウザーへ明示します。

ルート遷移後の再表示

Docsify はハッシュルーターを使用し,記事を開くと URL のハッシュが #/2026/weekly/CW36.md のように変化します。$docsify.auto2top も有効ですが,ルート遷移後の表示状態を確実に初期化するには,サイトナビゲーション側の処理も必要です。第2章で説明した tabindex の追加と組み合わせることで,ホームを表示したときに先頭見出しへのスクロールによってサイトナビゲーションが非表示になる現象を防ぎます。

ルートが変わるたびに,次の処理を実行します。

js
function pinTop() {
  revealNav();
  if (isHome() && !hasHeadingTarget() && window.scrollY !== 0) {
    window.scrollTo(0, 0);
  }
  lastY = window.scrollY;
  sync();
}
function syncAfterRoute() {
  pinTop();
  routeTimers.forEach(clearTimeout);
  routeTimers = [setTimeout(pinTop, 50)];
}

syncAfterRoute() は,次の二つの契機で呼び出されます。

  • hashchange
    ブラウザーのハッシュが変化した時点で呼び出します。
  • siteChromedoneEach
    Docsify が各ルートの本文を描画し終えた時点で呼び出します。描画後にスクロール位置が変化する場合に備え,50ms 後にも pinTop() を実行します。

isHome() は,現在のハッシュが記事を指しているかどうかを currentReportPath() によって判定します。ホームであり,?id= 形式の見出しターゲットがなく,現在位置が先頭でない場合に限って scrollTo(0, 0) を実行します。見出しアンカーがある場合は,その見出しへの移動を優先します。

ハッシュルーティングと通常 URL の分離

サイトナビゲーションのリンクには通常のパスを使用し,Docsify のハッシュは引き継ぎません。Home,Significant Bit,About のいずれかを選択すると,Real Terms の単一ページアプリケーションから別のページへ遷移します。

Real Terms 内の記事移動は,サイドバーと本文リンクが担います。これらのリンクは #/… 形式のハッシュルーティングを使用します。サイトナビゲーションは現在のサイトを示し,サイドバーはそのサイト内の現在の記事を示します。公式 Navbar を採用しないことで,サイト間の移動とドキュメント内の移動を明確に分離しています。