2022-06-10

【React】#付きリンクが機能しない時

外部からページ内リンク(#+ハッシュのURL)に飛んできた時に対象のアンカーリンクへ画面がスクロールしない問題を解決

Article Image

ページ内リンクへ飛べない

外部サイトからの遷移でURLにページ内リンクが入っていた時...

つまりURLが「/blog/2022/#見出し」のようになっていたとき

この時に対象の見出しまで画面がスクロールしないので困っていた。

この#はアンカーリンクと呼ぶ人もいれば、ハッシュとかとも呼ばれていたりするのでどれが正しいかよくわからない。

単純なページ内リンクは正常動作

私のケースではページ内の目次から正しく目的の見出しまでスクロールできるので、HTMLタグそのもの自体は正しいことがわかっている。

ではなぜ外部サイトから移ってきたときだけ遷移しないのだろうか。

原因

今回の場合は遷移直後に「ロード画面」が用意されていたのが問題。

このロード画面は非常に僅かな時間でスマホ判定をしているだけなのだが、既にそのタイミングでページ内遷移判定が行われてしまうようだ。

したがってまだ見出しが描画されていない状態でスクロールするかどうかが判定されるので、#付きの見出しが存在せず画面は一番上のままになってしまう。

jsでページ内遷移

というわけでロードが終わってからjs側でページ内遷移を行えばよい。

これがとても簡単で

window.location.hash = "#遷移したいハッシュ"

のコードを実行してやれば画面が対象の見出しまでスクロールする。

また遷移してきた瞬間にwindow.location.hashの中に#以降のハッシュが格納されているので

const test = window.location.hash

のようなコードでURL中の#指定したハッシュ値を取り出すことができる。

あくまで正しくアンカーリンクが設定されている前提

遷移しない時がある

ただ、このコードを正しいタイミングで実行しても画面遷移が発生しないケースがある。

window.location.hashもともと入っている値と全く同じ値を入れてもスクロールが作動しないという仕組みがあるからだ。

例えば外部から#testというハッシュで遷移してきた場合、適当なタイミングでwindow.location.hash = "#test"を呼び出しても画面は遷移しない。

前回と全く同じハッシュにスクロールしたい場合は一度クリアしてから再設定してやると動作する

つまり

const hashSave = window.location.hash
window.location.hash = ""
window.location.hash = hashSave

こんなコードを書いてやれば画面は再び対象のアンカーへスクロールする。

Reactではどうやるか

描画が終わった後(アンカー付きの見出しが書き出された後)にこの処理が走れば良いのでuseEffect()を使えばよい。

よって最終的なコードは

  React.useEffect(() => {
    if (window.location.hash && 最初のロード終了フラグ) {
      const hashSave = window.location.hash
      window.location.hash = ""
      window.location.hash = hashSave
    }
  },[
    //ここにも最初のロード終了のフラグ  
    ])

これで外部サイトから遷移してきた後、画面が描画されるのを待ってからページ内遷移の処理がもう一度走るはずだ。

以上

一応解決には至ったが、このコードの書き方が冗長というか昨今のプログラムっぽくない(あまりスマートなコードではない)ので実は別の方法があるかもしれない。

また発見があれば更新する。


この記事のタグ


謎の技術研究部 (謎技研)