You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无限滚动分页下浏览器后退按钮异常问题排查求助

无限滚动分页后退问题修复方案

问题描述

实现了无限滚动加载功能,滚动至页面底部时URL会自动更新为/in/top-wear.html?page=2、?page=3等形式,但点击浏览器后退按钮时,会直接离开当前页面,无法回到上一分页(比如从page=4退到page=3)。

原代码

useEffect(() => {
    console.log("🐇 top", categoryLoading);
    const handleScroll = () => {
        if(categoryLoading) return;
        const sumOfInnerHeightAndScrollY = (window.innerHeight + window.scrollY);
        const offsetHeight = document.body.offsetHeight;
        const scrollBottomPercentage = ((offsetHeight-sumOfInnerHeightAndScrollY)/offsetHeight)*100;

        if (scrollBottomPercentage < 25) {
            const searchParams = new URLSearchParams(window.location.search);
            const currentPage = parseInt(searchParams.get('page') || 1);
            const nextPage = currentPage + 1;

            console.log("🐇 scroll bottom", paginationButton);

            searchParams.set('page', nextPage);

            history.push({ search: searchParams.toString() }); //"page=2 and so on..."
        }
    };

    window.addEventListener('scroll', handleScroll);

    return () => {
        window.removeEventListener('scroll', handleScroll);
    };
}, [history, categoryLoading]);    

问题根源

  1. 滚动事件频繁触发:滚动到阈值区域时,handleScroll会被多次调用,导致同一个分页值被多次推入历史栈,后退时需要多次点击才能回到上一页;甚至初始页未被加入历史栈,第一次后退直接离开当前页面。
  2. 无重复触发拦截:没有锁机制,即使正在加载下一页,滚动仍会触发history.push,导致历史栈混乱。
  3. 无后退事件监听:点击后退后URL变化,但页面数据未同步更新,用户看不到上一页内容,误以为后退无效。

修复方案

1. 添加历史推送锁,避免重复触发

新增状态变量标记是否正在更新历史记录,防止滚动事件多次调用history.push。

2. 初始化历史栈

确保初始页(无page参数时)被替换为?page=1,避免后退时回到无参数的页面。

3. 优化滚动触发条件

改用固定像素阈值替代百分比,减少页面高度变化时的误触发。

4. 监听后退事件,同步页面数据

当用户点击后退,自动加载对应分页的数据,确保页面内容与URL一致。

修复后的完整代码

import { useEffect, useState } from 'react';

const TopWearPage = ({ history, categoryLoading, loadCategoryData }) => {
  // 历史推送锁,防止重复触发
  const [isPushingHistory, setIsPushingHistory] = useState(false);

  useEffect(() => {
    // 初始化:如果URL没有page参数,替换为page=1(避免后退到无参数页面)
    const initialParams = new URLSearchParams(window.location.search);
    if (!initialParams.get('page')) {
      initialParams.set('page', '1');
      history.replace({ search: initialParams.toString() });
    }

    const handleScroll = () => {
      // 加载中或正在推送历史时,直接返回
      if (categoryLoading || isPushingHistory) return;

      // 改用固定像素阈值(距离底部200px时触发,可根据需求调整)
      const distanceToBottom = document.body.offsetHeight - (window.innerHeight + window.scrollY);
      if (distanceToBottom < 200) {
        const searchParams = new URLSearchParams(window.location.search);
        const currentPage = parseInt(searchParams.get('page') || 1);
        const nextPage = currentPage + 1;

        // 上锁,防止重复触发
        setIsPushingHistory(true);
        // 更新URL历史
        searchParams.set('page', nextPage);
        history.push({ search: searchParams.toString() });
        // 加载下一页数据,加载完成后解锁
        loadCategoryData(nextPage).finally(() => {
          setIsPushingHistory(false);
        });
      }
    };

    // 监听后退事件,同步加载对应分页数据
    const handlePopState = () => {
      const searchParams = new URLSearchParams(window.location.search);
      const currentPage = parseInt(searchParams.get('page') || 1);
      loadCategoryData(currentPage);
    };

    window.addEventListener('scroll', handleScroll);
    window.addEventListener('popstate', handlePopState);

    // 清理事件监听
    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('popstate', handlePopState);
    };
  }, [history, categoryLoading, loadCategoryData]);

  // 组件渲染逻辑
  return <div>{/* 页面内容 */}</div>;
};

export default TopWearPage;

关键说明

  • isPushingHistory锁:确保一次滚动只触发一次历史推送,避免历史栈出现重复条目。
  • history.replace初始化:替换初始页的URL,不会增加额外历史条目,保证后退链的起点是page=1。
  • popstate监听:后退时自动加载对应分页数据,让页面内容与URL保持一致。
  • 固定像素阈值:比百分比阈值更稳定,避免页面高度较小时的误触发。

内容的提问来源于stack exchange,提问作者Abhishek kamal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:13:19