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

React.js如何检测用户滚动至屏幕底部?

检测滚动至屏幕底部触发数据加载(基于Intersection Observer)

针对你的场景,用Intersection Observer实现滚动到底部加载更多的核心是添加一个哨兵元素(sentinel),放在产品列表的末尾。当这个元素进入视口时,就触发数据获取。下面是适配你项目的完整实现:

完整代码实现(Products组件)

import { useEffect, useRef, useState } from 'react';

function Products() {
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const sentinelRef = useRef(null); // 哨兵元素的引用

  // 替换为你实际的API请求逻辑
  const fetchProducts = async () => {
    setIsLoading(true);
    try {
      const res = await fetch(`/api/products?page=${page}`);
      const newItems = await res.json();
      setProducts(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    } catch (err) {
      console.error('加载产品失败:', err);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    // 初始化Intersection Observer
    const observer = new IntersectionObserver(
      (entries) => {
        const [entry] = entries;
        // 只有当哨兵进入视口且未在加载时,才触发请求
        if (entry.isIntersecting && !isLoading) {
          fetchProducts();
        }
      },
      {
        root: null, // 使用浏览器视口作为根容器(对应页面滚动)
        rootMargin: '0px',
        threshold: 0.1 // 哨兵元素10%进入视口时触发
      }
    );

    const currentSentinel = sentinelRef.current;
    if (currentSentinel) observer.observe(currentSentinel);

    // 组件卸载时停止监听,避免内存泄漏
    return () => {
      if (currentSentinel) observer.unobserve(currentSentinel);
    };
  }, [isLoading, page]);

  return (
    <div className="gallery">
      {/* 渲染产品列表 */}
      {products.map(product => (
        <div key={product.id} className="product-item">
          <h3>{product.name}</h3>
          <p>{product.description}</p>
        </div>
      ))}

      {/* 加载状态提示 */}
      {isLoading && <div className="loading">加载中...</div>}

      {/* 哨兵元素 - 放在列表最后 */}
      <div ref={sentinelRef} className="sentinel"></div>
    </div>
  );
}

export default Products;

关键细节说明

  1. 哨兵元素的作用:
    哨兵是一个不可见的占位元素(可以通过CSS设置极小高度),放在产品列表末尾。相比直接监听最后一个产品元素,哨兵更稳定——即使产品列表更新,它的位置始终在最后,不会因为元素删除/新增导致监听失效。

  2. Observer配置要点:

    • root: null:如果是整个页面滚动,必须设为null(对应浏览器视口)。如果你之前错误地把root设为.gallery,但.gallery没有设置overflow: auto(即没有自己的滚动条),Observer会完全失效。
    • threshold: 0.1:调整触发时机,0表示只要哨兵有像素进入视口就触发,1表示完全进入才触发。根据你的需求选择合适的值。
  3. 加载状态控制:
    用isLoading状态防止重复请求——滚动时如果正在加载数据,不会再次触发fetchProducts,避免接口重复调用或数据重复。

  4. 清理函数:
    组件卸载时调用unobserve停止监听,防止内存泄漏。

配套CSS(可选)

给哨兵元素和列表加基础样式,确保布局正常:

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem;
  padding: 1rem;
}

.product-item {
  border: 1px solid #eee;
  padding: 1rem;
  border-radius: 8px;
}

.loading {
  text-align: center;
  padding: 2rem;
  grid-column: 1 / -1;
}

.sentinel {
  height: 1px;
  width: 100%;
  grid-column: 1 / -1;
}

你之前可能踩的坑

  • 错误设置了root元素:如果.gallery没有滚动条,却把它设为root,Observer无法检测到滚动。
  • 没有用哨兵元素:直接监听最后一个产品,当产品被移除时,监听的元素消失,Observer失效。
  • 未处理加载状态:导致滚动时多次触发请求,引发数据错误。

内容的提问来源于stack exchange,提问作者Mel Alejandrino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:18:27