⬆️

【Next.js × React × Typescript】URIフラグメントを実装する

に公開

初めに

こんにちは!Webサイトを閲覧していて、「トップへ戻る」ボタンをクリックしたり、目次の 「第二章」 をタップしたりすると、ページがスルスルと特定の場所にジャンプする体験をしたことはありませんか?
最初にこの動作を見たとき、「これって一体どうやって実装しているんだろう?」と不思議に思い、実装方法についてまとめてみました。

そもそもURIフラグメントって何?

URIフラグメントは、URIの一部であり、特定のURIが指し示すリソース内の「特定の箇所」を識別するために使われます。URIの末尾に「#」記号に続いて記述される文字列がフラグメントです。

  1. 「#」以降の部分: URIの中で「#」に続く部分がフラグメントです。

  2. クライアントサイドでの処理: フラグメントは、Webサーバーには送信されません。ブラウザなどのクライアント側で処理されます。サーバーは「#」より前の部分のみを解釈し、リソース全体を返します。クライアントは、受け取ったリソースの中で、フラグメントが指し示す特定の部分(例: HTML内の特定のIDを持つ要素)へ移動したり、その部分を強調表示したりします。

実装編

今回は、Next.js×React×Typescriptで実装していきたいと思います。

page.tsx
'use client';

import { useEffect, useRef } from 'react';
import Link from 'next/link';

export default function HomePage() {
  const refs = {
    section1: useRef<HTMLDivElement>(null),
    section2: useRef<HTMLDivElement>(null),
    top: useRef<HTMLDivElement>(null),
  };

  const scrollToSection = () => {
    const hash = window.location.hash.replace('#', '');
    const targetRef = refs[hash as keyof typeof refs];
    if (targetRef?.current) {
      targetRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  };

  useEffect(() => {
    scrollToSection();
    window.addEventListener('hashchange', scrollToSection);
    return () => window.removeEventListener('hashchange', scrollToSection);
  }, []);

  return (
    <main>
      <h1>Next.js URI フラグメント デモ</h1>

      <nav>
        <Link href="#top">トップへ</Link>
        <Link href="#section1">Section 1</Link>
        <Link href="#section2">Section 2</Link>
      </nav>
      
      <div id="section1" ref={refs.section1} className="section">
        <h2>Section 1</h2>
        <p>ここがセクション1です。</p>
      </div>

      <div id="section2" ref={refs.section2} className="section">
        <h2>Section 2</h2>
        <p>ここがセクション2です。</p>
      </div>

      <Link href="#top">トップへ</Link>
    </main>
  );
}

詳細
今回は、useEffect, useRefを使用して実装しました。

useRefってなーに

以下に詳しくまとめています。
https://zenn.dev/daichi09167/articles/93b3aa8ae7be3b

  • セクションごとにDOMへの参照(ref)を作る。
  • useRef<HTMLDivElement>(null):最初はnull、あとで実際のDOMが入る。
  • オブジェクトでまとめてるので、refs.section1のようにアクセス可能。

globals.css
main {
  padding: 20px;
}

nav {
  display: flex;
  flex-direction: column; /* ← 縦並びにする */
  gap: 10px;               /* 各リンクの間に余白 */
}

nav a {
  text-decoration: underline;
  color: #0070f3;
}
nav a:hover {
  color: #005bb5; /* ホバー時の色 */
}


.section {
  height: 100vh;
  padding: 20px;
}

#section1 {
  background-color: #f2f2f2;
}

#section2 {
  background-color: #e2e2e2;
}

詳細
CSSは上記のような感じにしました。

実際の画面

まとめ

  • URIフラグメントは、URIの末尾に「#」記号に続いて記述される文字列のこと。
  • Next.jsやReactでは、useRef・window.location.hash・useEffectを組み合わせることで、スムーズなスクロールが実装できる。

Discussion