如何使用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背景色呼应 */ }
关键说明
- 容器引用:使用
useRef获取滚动容器DOM元素,用于计算容器宽度和控制滚动行为; - 分页计算:通过容器宽度和单个元素的总宽度(min-width + 左右margin),计算每页可容纳的元素数量及总页数;
- 动态适配:监听窗口
resize事件,容器尺寸变化时重新计算分页数据,保证适配不同屏幕; - 滚动控制:点击分页点时调用
scrollTo方法滚动到对应位置,配合behavior: 'smooth'实现平滑过渡; - 显示逻辑:仅当总页数大于1时,渲染分页点容器,满足“内容超出才显示”的需求。
内容的提问来源于stack exchange,提问作者manish thakur
相关产品推荐
相关产品推荐

