🪽

【React Native】WebView でアンカーリンクへ自動スクロールされない問題を解決する

に公開

WebView でアンカーリンク遷移できない

React Native モバイルアプリの WebView で以下のような #fragment 付き URL を開いたとき、ブラウザのようにアンカーリンクまでスクロールされない 時の対処法を紹介したいと思います。

https://example.com/#fragment

ブラウザならリンクを踏むだけでスクロールしてくれるのに…🤔

injectedJavaScript を使ってアンカーリンクへスクロールさせる

ステップ 1 — フラグメント(#fragment)を取り出す

まずは渡された URL からハッシュ部分だけを抜き出します。

// 例: https://example.com/#fragment  →  'fragment'
const parsedUrl = new URL(url);
const fragment = parsedUrl.hash ? parsedUrl.hash.slice(1) : null;

ステップ 2 — 挿入用スクリプトを組み立てる

抽出した fragment を使ってアンカーリンクへスクロールする JS 文字列を生成します。
要素が存在しない場合でも失敗しないようオプショナルチェイニング (?.) で呼び出します。

const buildInjectedScript = (url: string): string => {
  const parsedUrl = new URL(url);
  const fragment = parsedUrl.hash ? parsedUrl.hash.slice(1) : null;

  // 挿入する JavaScript コードを組み立てる
  // fragment があれば該当 ID の要素を探し、なければ null を設定
  // 100ms後に target?.scrollIntoView を実行
  return `
    setTimeout(() => {
      const target = ${
        fragment ? `document.getElementById('${fragment}')` : 'null'
      };
      target?.scrollIntoView({ behavior: 'smooth' });
    }, 100);
  `;
};

⏱ なぜ遅延させている?
ページサイズや通信状況によっては DOM が描画される前にスクリプトが走ってしまうことがあるためです。
遅延値は必要に応じて変更してください。

ステップ 3 — WebView に挿入する

React Native WebView の injectedJavaScript プロパティに、先ほど生成した文字列をそのまま渡します。
URL が変わるたびに再生成してあげればマルチページでも動作します。

import React, { useEffect, useState } from 'react';
import { WebView } from 'react-native-webview';

type Props = { uri: string };

export const AnchorWebView = ({ uri }: Props) => {
  const [injectedJs, setInjectedJs] = useState('');

  // uri が変更されたら挿入するスクリプトを再生成
  useEffect(() => {
    setInjectedJs(buildInjectedScript(uri)); // ← ステップ 2 で作成した関数を呼び出し
  }, [uri]);

  return (
    <WebView
      source={{ uri }}
      injectedJavaScript={injectedJs} // 生成したスクリプトを挿入
      javaScriptEnabled // JavaScript を有効にする
    />
  );
};

全コード

import React, { useEffect, useState } from 'react';
import { WebView } from 'react-native-webview';

type Props = { uri: string }; // 例: https://example.com/#fragment

/* ===== スクリプトを生成 ====================================== */
const buildInjectedScript = (url: string): string => {
  // URLからハッシュ部分(#fragment)を抽出
  const parsedUrl = new URL(url);
  const fragment = parsedUrl.hash ? parsedUrl.hash.slice(1) : null;

  // WebView に挿入する JavaScript コードを生成
  return `
    setTimeout(() => {
      // fragment があれば該当 ID の要素を探し、なければ null
      const target = ${
        fragment ? `document.getElementById('${fragment}')` : 'null'
      };
      // 100ms 後にスムーズスクロールで要素表示位置へ移動
      target?.scrollIntoView({ behavior: 'smooth' });
    }, 100);
  `;
};

/* ===== WebView コンポーネント =============================== */
export const AnchorWebView = ({ uri }: Props) => {
  const [injectedJs, setInjectedJs] = useState('');

  // uri が変更されるたびに挿入するスクリプトを更新
  useEffect(() => {
    setInjectedJs(buildInjectedScript(uri));
  }, [uri]);

  return (
    <WebView
      source={{ uri }} // 表示する URL
      injectedJavaScript={injectedJs} // 生成した JavaScript を挿入
      javaScriptEnabled // JavaScript の実行を許可
    />
  );
};

まとめ

  1. URL からハッシュを抽出: new URL(url).hash を利用。
  2. 遅延スクロール用 JS を生成: setTimeoutscrollIntoView を組み合わせる。
  3. injectedJavaScript に渡すだけで OK: WebView 側で実行される。

これでブラウザ同様アンカーリンク遷移を React Native アプリの WebView でも再現できます 🎉

Discussion