はじめに
Reactでシングルページアプリケーション(SPA)を作っていると
「ページ内リンク」と「ページ遷移+スクロール」を両立したい場面がよくあります。
この記事では、その実装パターンと注意点を整理します。
この記事で解決できること
- React Routerでページ遷移+スクロールを両立
- ページ内スクロールが機能しない原因と解決
- ハッシュ付き遷移の安定実装
状況 | URL | 挙動 |
---|---|---|
トップページ | /#section1 |
id="section1" にスクロール |
他ページ → トップ | /#section1 |
遷移後スクロール(ScrollToHash) |
ルーティング
<BrowserRouter basename="/your-path/">
<ScrollToHash />
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<TopPage />} />
<Route path="news" element={<NewsList />} />
<Route path="news/:newsId" element={<NewsDetail />} />
</Route>
</Routes>
</BrowserRouter>
※basenameはGitHub Pagesやサブディレクトリ配下用
ヘッダーのリンク
<Link to="/#section1" onClick={toggleMenu}>
トップ
</Link>
ポイント
-
to="/#section1"
とすることでトップページに遷移し、その後#section1
にスクロール - onClick はモバイルメニューを閉じる用など自由に使える
Scrollのためのコンポーネント
import { useLocation } from "react-router-dom";
import { useEffect } from "react";
export const ScrollToHash = () => {
const location = useLocation();
useEffect(() => {
// querySelector("#")はエラーにする
if (location.hash && location.hash.length > 1) {
const id = location.hash.replace("#", "");
// 0.1秒だけ待ってからDOMにアクセス(マウント直後対応)する
setTimeout(() => {
const target = document.getElementById(id);
if (target) {
target.scrollIntoView({ behavior: "smooth" });
}
}, 100);
}
}, [location]);
return null;
};
はまりポイント
①ハッシュだけだとエラー
document.querySelector("#");
→ "#"は不正なCSSセレクタなのでエラーになります。
→ "#"や""を除外する。
②ページ遷移直後はDOM未描画
→ スクロール対象のid="section1"
がまだDOMにない。
→ setTimeout
で遅延実行する。
補足コラム的な
HashRouterとBrowserRouterの落とし穴
ルーター | URL例 | 特徴 |
---|---|---|
BrowserRouter | /your-path/news |
URLキレイ、本番は要サーバー設定 |
HashRouter | /#/news |
デプロイ楽、GitHub Pages向き |
basenameはBrowserRouter専用なので、HashRouterにbasenameは不要。
この2つを途中で切り替えて動かないのはよくあるっぽい。
まとめ
React Routerでハッシュ付きスクロールを実装するには、下記を覚えておこうと思います。
-
Link to="/#id"
に統一する - ScrollToHash を使う
- DOM描画タイミングに注意