首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

JavaScript 精準判斷瀏覽器滾動到底部:兼容模式、視口高度與滾動事件節流完整方案

     編輯:程式設計疑難解答

「滾動到底部加載更多」是無限滾動核心需求。但 `scrollTop + clientHeight >= scrollHeight` 看似簡單,實則暗藏 **CSS1Compat 模式差異、文檔/元素滾動容器混淆、邊界閾值抖動** 等大坑。本文提供跨瀏覽器、高性能、生產級的底部檢測方案。

核心難點:`document.body` vs `document.documentElement`

在 **標準模式** (``) 下:

  • 頁面整體滾動容器是 **`document.documentElement`** (``),而非 `body`。
  • `document.body.scrollTop` **恆為 0** (Chrome/FF/Edge 標準行為),Safari 早期版本例外。
  • 視口高度:`document.documentElement.clientHeight` (標準模式) / `document.body.clientHeight` (怪異模式)。
  • 滾動高度:`document.documentElement.scrollHeight` (整個文檔高度)。

生產級檢測函數 (原生 JS,無依賴)


/**
 * 判斷是否滾動到底部 (含閾值緩衝)
 * @param {number} threshold - 提前觸發閾值(像素),默認 0 即精確底部
 * @param {Element} container - 滾動容器,默認 window (文檔)
 * @returns {boolean}
 */
function isScrolledToBottom(threshold = 0, container = window) {
    // 1. 獲取滾動容器的關鍵尺寸
    let scrollTop, scrollHeight, clientHeight;
    
    if (container === window) { // 文檔滾動
        const doc = document.documentElement; // 標準模式關鍵!
        const body = document.body;
        
        // 兼容性獲取 scrollTop (標準模式用 doc, 怪異模式/舊瀏覽器用 body)
        scrollTop = window.pageYOffset || doc.scrollTop || body.scrollTop || 0;
        
        // scrollHeight 取最大值,防止 body 有 margin 導致 doc.scrollHeight < 實際
        scrollHeight = Math.max(doc.scrollHeight, body.scrollHeight, doc.offsetHeight, body.offsetHeight);
        
        clientHeight = doc.clientHeight; // 視口高度
    } else { // 元素滾動 (如 div 內部滾動)
        scrollTop = container.scrollTop;
        scrollHeight = container.scrollHeight;
        clientHeight = container.clientHeight;
    }
    
    // 2. 核心判斷:滾動距離 + 視口高度 >= 總高度 - 閾值
    return (scrollTop + clientHeight >= scrollHeight - threshold);
}

滾動事件綁定:必加節流防抖

直接綁 `scroll` 事件會極其頻繁觸發 (每幀數次),導致主執行緒卡頓、重複觸發加載。必須使用 **`requestAnimationFrame` 節流** 或 **時間戳節流**。


// 方案一:requestAnimationFrame 節流 (最流暢,配合瀏覽器繪製幀)
let ticking = false;
window.addEventListener('scroll', () => {
    if (!ticking) {
        ticking = true;
        requestAnimationFrame(() => {
            if (isScrolledToBottom(100)) { // 提前 100px 觸發加載
                loadMoreData();
            }
            ticking = false;
        });
    }
}, { passive: true }); // passive: true 提升滾動性能

// 方案二:時間戳節流 (簡單,固定間隔)
let lastTime = 0;
const THROTTLE_MS = 200;
window.addEventListener('scroll', () => {
    const now = Date.now();
    if (now - lastTime >= THROTTLE_MS) {
        lastTime = now;
        if (isScrolledToBottom(50)) loadMoreData();
    }
}, { passive: true });

完整無限滾動加載骨架

let loading = false; // 防並發鎖
let page = 1;
const THRESHOLD_PX = 200; // 提前 200px 預加載

async function loadMoreData() {
    if (loading) return;
    loading = true;
    showLoadingSpinner();
    
    try {
        const data = await fetch(`/api/list?page=${page++}&size=20`).then(r => r.json());
        if (data.length === 0) {
            showEndMessage();
            window.removeEventListener('scroll', onScroll); // 移除監聽
            return;
        }
        appendItemsToDOM(data);
    } catch (e) {
        console.error('加載失敗', e);
        showRetryButton();
    } finally {
        loading = false;
        hideLoadingSpinner();
    }
}

function onScroll() { /* 節流邏輯調用 isScrolledToBottom(THRESHOLD_PX) */ }
window.addEventListener('scroll', onScroll, { passive: true });

常見坑點避雷表

現象原因修正
永遠不觸發 / 過早觸發用 `document.body.scrollTop` (標準模式下恆 0)統一用 `document.documentElement` 或 `window.pageYOffset`
觸發多次重複加載無 `loading` 鎖、無節流、閾值為 0 邊界抖動加 `loading` 旗標 + `requestAnimationFrame` 節流 + `threshold >= 50px`
移動端地址欄隱藏導致高度變化誤判視口高度 `clientHeight` 動態變化用 `window.visualViewport.height` (現代移動端) 或增大閾值容忍
iframe/Shadow DOM 內滾動容器非 window傳入具體滾動容器 `element` 給 `isScrolledToBottom`

總結

**標準模式下認準 `document.documentElement`**,封裝兼容性 `getScrollMetrics()`,配合 **`requestAnimationFrame` 節流 + `loading` 鎖 + 預載閾值**,即可構建流暢、可靠、跨端的無限滾動體驗。避免直接複製網上簡易版 `body.scrollTop` 代碼,那是怪異模式時代的遺物。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved