内部リンクから「戻る」を押すと、PCでは元の位置に戻るのに、スマホはページの先頭に戻ってしまう。これ、地味にストレスです。AIと試行錯誤しながら、JavaScriptの追加で解決できました。
発生していた問題
内部リンクのリンク先に飛んだあと、「戻る」ボタンで戻ると、読んでいた位置ではなくページ先頭に戻ってしまいます。
そこから元の場所までスクロールし直すのは、なかなか面倒です。書いている本人ですら不便なのだから、読んでくれている人はなおさらでしょう。
F-lightを使っている他の方のブログでは再現しなかったので、私のブログの環境やカスタマイズが関係しているのかもしれません。
Geminiに相談する
Geminiからは次の3つの方法が提案されました。
- 内部リンクを
target="_blank"にして新しいタブで開く - 目次を充実させて戻りやすくする
- JavaScriptでスクロール位置を復元する
1はタブが増えてしまいますし、2は根本的な解決ではありません。 求めているのは3です。
またテーマを触ることになりますが、最近はこういうカスタマイズが少し楽しくなってきています。
コードの試行錯誤
試行錯誤は長いので完成版だけ見たい場合はこちらへ
Geminiは何か抜けます。でも指摘すると、どんどんコードを足してきます。
ChatGPTは目の前の問題を徹底的に潰そうとするので、別のところに副作用が出がちです。
この2人を対話させながら作っていくと良いコードができることを前回学習しました。
1.まずはGemini
Geminiは「数行で解決できます」と言ってくれました。「ブラウザに位置を覚えさせる」数行のコードです。
<script>
if ('scrollRestoration' in history) {
history.scrollRestoration = 'auto';
}
</script>
結果は変わりませんでした。PCでは問題なく戻るものの、スマホでは相変わらずページの先頭へ。
まあ、一発でうまくいくとは思っていませんけど。
2.これをChatGPTに聞いてみる
ChatGPTに見せると、「それだけでは不安です。これくらいは書きましょう」とあっさり却下。
これくらいは書かないと不安ですって。
<script>
// ブラウザ標準
if ('scrollRestoration' in history) {
history.scrollRestoration = 'auto';
}
document.addEventListener("DOMContentLoaded", function () {
const key = location.pathname;
// 保存
document.querySelectorAll("a").forEach(link => {
link.addEventListener("click", function () {
sessionStorage.setItem(key, window.scrollY);
});
});
// 復元
const scrollY = sessionStorage.getItem(key);
if (scrollY !== null) {
window.scrollTo(0, parseInt(scrollY));
}
});
</script>
進展はありました。でもスマホでは戻らないこともあり、まだイマイチ不安定です。
3.再度Gemini
ChatGPTの指摘をGeminiに伝えます。すると、「なるほど」とコードを書き直してくれました。
<script>
// <![CDATA[
(function() {
const key = "scroll_pos_" + location.pathname;
// 1. リンククリック時に現在の位置を保存
document.addEventListener("click", function(e) {
const target = e.target.closest("a");
if (target && target.href.includes(location.hostname)) {
sessionStorage.setItem(key, window.scrollY);
}
});
// 2. ページ読み込み時に復元
window.addEventListener("load", function() {
// ブラウザの「戻る・進む」でのアクセスか確認
const navType = performance.getEntriesByType("navigation")[0]?.type;
if (navType === "back_forward") {
const scrollY = sessionStorage.getItem(key);
if (scrollY !== null) {
// 画像などの読み込み完了を待ってから少しだけ遅延させてジャンプ
setTimeout(() => {
window.scrollTo(0, parseInt(scrollY));
}, 100);
}
}
});
// 標準機能も一応併用
if ('scrollRestoration' in history) {
history.scrollRestoration = 'auto';
}
})();
// ]]>
</script>
直すたびに修正が増えていき、コードもどんどん長くなっていく、Geminiも本気を出してきています😅
Geminiに「コードが長すぎる」と伝えたら、かなり短くしてくれました。
<script>
// <![CDATA[
document.addEventListener("DOMContentLoaded", function () {
const key = "save_scroll_" + location.pathname;
// 1. リンククリック時に位置を記録
document.addEventListener("click", e => {
if (e.target.closest("a")) sessionStorage.setItem(key, window.scrollY);
});
// 2. ページ読み込み時に位置を復元(戻ったときだけ)
const scrollY = sessionStorage.getItem(key);
if (scrollY && window.performance.navigation.type === 1) {
window.scrollTo(0, parseInt(scrollY));
}
});
// ]]>
</script>
ところが判定が間違っていて、「戻る」ではなくリロード時しか動かないコードになってしまいました。
4.もう一度ChatGPT
ChatGPTが自信満々(いつもだけど)で出してきたコードがこちら。今見ると、この時点でかなり最終版に近いです。
<script>
document.addEventListener("DOMContentLoaded", function () {
const key = "save_scroll_" + location.pathname;
// 保存(軽量)
document.addEventListener("click", e => {
if (e.target.closest("a")) {
sessionStorage.setItem(key, window.scrollY);
}
});
// 戻る時だけ復元
const navEntries = performance.getEntriesByType("navigation");
if (navEntries.length && navEntries[0].type === "back_forward") {
const scrollY = sessionStorage.getItem(key);
if (scrollY !== null) {
setTimeout(() => {
window.scrollTo(0, parseInt(scrollY));
}, 100);
}
}
});
</script>
ChatGPTは「これで大丈夫」と言いますが、実際に試してみるとスマホでは完全ではありませんでした。
試行錯誤の末、問題はスクロール位置の保存ではなく、復元を実行するタイミングにあることが分かりました。
そこで目を付けたのがpageshowイベントです。
導入したJavaScriptコード
何度も試し、pageshowとe.persistedを組み合わせた現在のコードにたどり着きました。
⚠️ バックアップを取ってからお試しください
<script>
// <![CDATA[
(function() {
const key = "scroll_" + location.pathname;
// 1. リンクをクリックした瞬間の場所をメモする
document.addEventListener("click", () => {
sessionStorage.setItem(key, window.scrollY);
});
// 2. 「戻る」でページが表示された時だけ、メモを読み取ってジャンプする
window.addEventListener("pageshow", (e) => {
const nav = performance.getEntriesByType("navigation")[0];
if (e.persisted || (nav && nav.type === "back_forward")) {
const pos = sessionStorage.getItem(key);
if (pos !== null) {
// 画像読み込み後のズレ防止に少しだけ待ってからスクロール
setTimeout(() => window.scrollTo(0, parseInt(pos)), 50);
}
}
});
})();
// ]]>
</script>
このコードがやっていること
- クリック時にメモする: 読者がリンクを踏んだ瞬間、その記事の「何ピクセル目まで読んだか」をブラウザに一時保存(sessionStorage)させます。
- 戻る操作に対応する: iPhoneのSafariをはじめ、一部のブラウザは「戻る」時にページを再読み込みせず、一時保存したページ(BFCache)をそのまま表示します。そのため通常の読み込みイベントではなく、画面が再表示されたときにも実行される「pageshow」を使っています。
- 少しだけ待ってから動く: 戻った直後は画像の読み込みなどでページ全体の高さがガタつくことがあるため、50ミリ秒(一瞬)待ってから元の位置へ強制ジャンプさせています。
スクロール位置がページの先頭(0px)の場合でも正しく動くようにするなど、細かな部分も考慮しています。
今はこのブログ『さあ遊ぶぞ!!』にも導入していて、スマホでの「戻る」が以前よりずっと快適になりました。
JavaScriptの内容は正直ほとんど分かっていません。でも、AIと試行錯誤しながら少しずつ形になっていく過程もブログ運営の楽しさです。今回も一つ勉強になりました。
コメントを投稿
別ページに移動します