[料理佳餚] 使用 window.history 來實作儲存及還原瀏覽器的捲軸位置

網頁瀏覽器都有一個功能,它會幫我們記住目前頁面的捲軸位置,在我們重新整理或是回到上一頁、前往下一頁之後,捲軸會還原到原本的位置,可是如果我們的網頁 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);

});

下圖是執行結果:

以上,儲存及還原瀏覽器捲軸的作法就分享給有需求的朋友,希望有幫到一點點忙。

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學