網頁瀏覽器都有一個功能,它會幫我們記住目前頁面的捲軸位置,在我們重新整理或是回到上一頁、前往下一頁之後,捲軸會還原到原本的位置,可是如果我們的網頁 HTML 元件是在前端產生的,因為畫面上 HTML 元件的渲染時機不定,那麼瀏覽器在還原捲軸位置的時候,就會有不穩定的情況,不是無效不然就是位置不對,這個問題我們透過 window.history 就能輕鬆解決,我們來看一下怎麼做?
實驗環境
我建立了一個 ScrollbarRestorationLab
的專案,用 setTimeout() 來模擬等待 Web Api 回應,等待回應完畢之後,用 jQuery 在畫面上產生 100 個高度 50px 的 div 元件,如下所示:
儲存及還原捲軸位置
從上面的圖中,大家應該多少也能看得出來,重新整理之後捲軸並沒有維持在原本的位置,所以接下來,我就要來撰寫儲存及還原捲軸位置的程式,大致上的邏輯是這樣的,監聽 scroll 事件,當捲軸位置發生改變的時候,將捲軸的位置透過 window.history.replaceState 儲存在歷程記錄裡面,當重新整理或是回到上一頁、前往下一頁之後,等待 HTML 元件渲染完畢,再將捲軸位置從歷程記錄中取出來,用 window.scrollTo() 將捲軸移到指定的位置,程式碼如下:
// 關閉自動捲動,改由手動。
window.history.scrollRestoration = "manual";
$(function () {
// 模擬等待 Web Api
setTimeout(() => {
// 產生 100 個 50px 的 div 元件
for (let i = 0; i < 100; i++) {
$(".text-center").append($(`<div style="height: 50px;">Div-${i}</div>`));
}
// 取得目前的歷程狀態
const currentState = window.history.state;
// 判斷是否有儲存捲軸的位置
if (currentState && (currentState.__scrollX || currentState.__scrollY)) {
const bodyRect = document.body.getBoundingClientRect();
// 判斷 body 的寬高是否有大於儲存的捲軸位置
if (bodyRect.width >= currentState.__scrollX && bodyRect.height >= currentState.__scrollY) {
// 瞬間移動捲軸到指定位置
window.scrollTo({
left: currentState.__scrollX,
top: currentState.__scrollY,
behavior: "instant"
});
}
}
// 暫存原生的 window.history.replaceState 方法
const _replaceState = window.history.replaceState;
// 覆寫 window.history.replaceState,順手將捲軸位置儲存下來。
window.history.replaceState = function (state, ...args) {
state = state || {};
state.__scrollX = window.scrollX;
state.__scrollY = window.scrollY;
_replaceState.apply(window.history, [state].concat(args));
}
// 監聽 scroll 事件,捲軸位置改變時,呼叫 window.history.replaceState 方法。
let scrollTimer;
window.addEventListener("scroll", function () {
if (scrollTimer) clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
window.history.replaceState(window.history.state, document.title, window.location);
}, 50);
});
}, 200);
});
下圖是執行結果:
以上,儲存及還原瀏覽器捲軸的作法就分享給有需求的朋友,希望有幫到一點點忙。