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

如何使用React Hooks实现带分页点的横向滚动功能

React横向滚动列表的分页点实现方案

我正在开发一个横向展示多个<div>的模块,目前通过CSS的overflow-x:auto实现了内容超出时的横向滚动效果,但需要添加分页点功能,具体信息如下:

已实现代码

JSX 代码

<div className="App d-flex">
  {data.map((li) => {
    return (
      <div key={li.id} className="boxes">
        {li.name}
      </div>
    );
  })}
</div>

CSS 代码

.d-flex {
  display: flex;
}
.boxes {
  padding: 12px;
  margin: 4px;
  background-color: rgb(196, 218, 243);
  min-width: 150px;
  overflow-x: auto;
  text-align: center;
  border-radius: 6px;
}

需求说明

  • 添加分页点,点击分页点可触发对应页面的横向滚动;
  • 仅当内容超出一屏可显示数量时,才展示分页点;
  • 使用React Hooks实现该功能。

补充说明

分页点对应每页内容:例如一屏能显示4个<div>则不显示分页点;若有8个则显示2个分页点,点击对应分页点可直接滚动到对应页面。


实现方案

核心思路

通过React Hooks获取滚动容器的DOM引用,计算每页可容纳的元素数量、总页数,再通过状态管理控制分页点的显示与滚动逻辑,同时监听窗口尺寸变化动态更新分页数据。

完整代码实现

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

function App() {
  // 示例数据,可替换为你的业务数据
  const data = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
    { id: 4, name: 'Item 4' },
    { id: 5, name: 'Item 5' },
    { id: 6, name: 'Item 6' },
    { id: 7, name: 'Item 7' },
    { id: 8, name: 'Item 8' },
  ];
  const scrollContainerRef = useRef(null);
  const [currentPage, setCurrentPage] = useState(0);
  const [totalPages, setTotalPages] = useState(0);
  const [showPagination, setShowPagination] = useState(false);
  // 单个box的总宽度:min-width(150) + 左右margin(4*2)
  const itemTotalWidth = 158;

  // 计算分页相关数据
  const calculatePagination = () => {
    if (!scrollContainerRef.current) return;
    const containerWidth = scrollContainerRef.current.clientWidth;
    const itemsPerPage = Math.floor(containerWidth / itemTotalWidth);
    const total = Math.ceil(data.length / itemsPerPage);
    setTotalPages(total);
    // 仅总页数大于1时显示分页点
    setShowPagination(total > 1);
  };

  // 初始化及窗口尺寸变化时重新计算分页
  useEffect(() => {
    calculatePagination();
    window.addEventListener('resize', calculatePagination);
    return () => window.removeEventListener('resize', calculatePagination);
  }, [data.length]);

  // 点击分页点触发滚动
  const handlePageClick = (pageIndex) => {
    if (!scrollContainerRef.current) return;
    setCurrentPage(pageIndex);
    const containerWidth = scrollContainerRef.current.clientWidth;
    const itemsPerPage = Math.floor(containerWidth / itemTotalWidth);
    scrollContainerRef.current.scrollTo({
      left: pageIndex * itemsPerPage * itemTotalWidth,
      behavior: 'smooth'
    });
  };

  // 监听滚动,同步当前页码(优化用户体验)
  useEffect(() => {
    const handleScroll = () => {
      if (!scrollContainerRef.current) return;
      const containerWidth = scrollContainerRef.current.clientWidth;
      const itemsPerPage = Math.floor(containerWidth / itemTotalWidth);
      const currentPosition = scrollContainerRef.current.scrollLeft;
      const currentPageIndex = Math.round(currentPosition / (itemsPerPage * itemTotalWidth));
      setCurrentPage(currentPageIndex);
    };

    const container = scrollContainerRef.current;
    if (container) {
      container.addEventListener('scroll', handleScroll);
    }
    return () => {
      if (container) {
        container.removeEventListener('scroll', handleScroll);
      }
    };
  }, [itemTotalWidth, data.length]);

  return (
    <div className="app-container">
      {/* 滚动容器 */}
      <div 
        ref={scrollContainerRef}
        className="scroll-container d-flex"
      >
        {data.map((li) => {
          return (
            <div key={li.id} className="boxes">
              {li.name}
            </div>
          );
        })}
      </div>
      {/* 分页点容器 */}
      {showPagination && (
        <div className="pagination-dots">
          {Array.from({ length: totalPages }).map((_, index) => (
            <button
              key={index}
              className={`dot ${currentPage === index ? 'active' : ''}`}
              onClick={() => handlePageClick(index)}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

对应CSS修改及新增

.app-container {
  position: relative;
  padding-bottom: 30px; /* 给分页点预留空间 */
}

.scroll-container {
  display: flex;
  overflow-x: auto;
  scroll-behavior: smooth; /* 开启平滑滚动 */
  /* 隐藏原生滚动条(可选) */
  scrollbar-width: none;
}

.scroll-container::-webkit-scrollbar {
  display: none;
}

.boxes {
  padding: 12px;
  margin: 4px;
  background-color: rgb(196, 218, 243);
  min-width: 150px;
  text-align: center;
  border-radius: 6px;
  flex-shrink: 0; /* 禁止元素被压缩变形 */
}

.pagination-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
}

.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: none;
  background-color: #ccc;
  cursor: pointer;
}

.dot.active {
  background-color: #639df0; /* 激活状态颜色,与box背景色呼应 */
}

关键说明

  1. 容器引用:使用useRef获取滚动容器DOM元素,用于计算容器宽度和控制滚动行为;
  2. 分页计算:通过容器宽度和单个元素的总宽度(min-width + 左右margin),计算每页可容纳的元素数量及总页数;
  3. 动态适配:监听窗口resize事件,容器尺寸变化时重新计算分页数据,保证适配不同屏幕;
  4. 滚动控制:点击分页点时调用scrollTo方法滚动到对应位置,配合behavior: 'smooth'实现平滑过渡;
  5. 显示逻辑:仅当总页数大于1时,渲染分页点容器,满足“内容超出才显示”的需求。

内容的提问来源于stack exchange,提问作者manish thakur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:24:55