使用 React Snap Carousel 在 React 中实现无限滚动
轮播是节省用户界面空间的好方法。轮播允许您在可滚动的容器中呈现内容列表,这些内容本来会占据用户界面的很大一部分,无论是垂直滚动还是水平滚动,但不会占用过多的屏幕空间。
虽然轮播有各种样式和类型,但从头开始构建轮播可能既耗时又复杂。在本文中,我将向您展示如何使用 React Snap Carousel 简化此过程 - 这是一个专门设计用于在 React 应用程序中快速轻松地实现轮播的库。
React Snap Carousel 入门
要开始使用 React Snap Carousel,请首先使用以下命令安装该包:
npm i react-snap-carousel
安装完成后,您可以将其集成到您的组件中。下面是如何使用该包的基本示例:
import { useSnapCarousel } from 'react-snap-carousel'; const items = Array.from({ length: 20 }).map((_, i) => ({ id: i, src: https://picsum.photos/500?idx=${i} })); export const Carousel = () => { const { scrollRef, next, prev} = useSnapCarousel(); return (); }{items.map((item, i) => ())}![]()
从示例中可以看出,React Snap Carousel 不会像许多其他轮播库一样公开预构建的组件。通常,此类组件会充当瑞士军刀的角色,在后台管理包的状态和功能。
采用这种方法的原因是 React snap 轮播是无头的。这意味着它没有预构建或开箱即用的样式组件。相反,它公开了一个 useSnapCarousel 函数,该函数提供对创建轮播所需的状态管理和功能的精细控制。
这样,您可以自由地完全根据您的特定需求定制视觉设计和样式。
为了简洁起见,我们将在本文中将 React Snap Carousel 称为“RSC”。
useSnapCarousel API
`useSnapCarousel` 函数是一个自定义 React hook,它公开了多个状态,可用于管理轮播元素(例如幻灯片、滚动行为和交互)。这些状态包括:
乍一看,这个列表似乎太多了,但不用担心。你只需要其中的几个子集——`scrollRef`、`next`、`prev` 和 `refresh`——就可以创建一个功能齐全的轮播,如上一节所示。
为了让您更好地理解这些功能如何协同工作,我们将逐步使用它们来构建完整的轮播组件。
创建一个功能齐全的轮播
创建轮播的第一步是准备要显示的数据。在前面的示例中,我们使用 `Array.from()` 方法生成静态数据:
const items = Array.from({ length: 20 }).map((_, i) => ({ id: i, src: https://picsum.photos/500?idx=${i} }));
然而,在实际应用中,您可能会异步获取数据,并在数据可用后呈现轮播。为了演示这一点,我们将使用 Unsplash API(一种开源图像 API)来获取轮播的随机图像列表。
首先在 React 项目中创建一个新组件。如果您尚未设置 React 项目,请查看我们的使用 Vite 设置 React 项目的指南。
此组件将保存轮播的逻辑。但是,为了保持组件整洁并分离逻辑,我们还将创建一个“useFetch”钩子来处理图像的获取。
通过在 `src/hooks/useFetch.js` 添加新文件来创建 `useFetch` 钩子并添加以下代码:
import React from 'react'; export const useFetch = (url) => { const [data, setData] = React.useState(); React.useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } const result = await response.json(); setData(result); } catch (error) { console.error(error.message); } }; fetchData(); }, [url]); return { data }; };
创建 `useFetch` 钩子后,返回到 Carousel 组件。导入钩子并将 Unsplash API 端点作为参数传递。然后,从钩子的返回值中解构 `data` 状态:
import { useFetch } from '../hooks/useFetch'; const Carousel = () => { const url = `https://api.unsplash.com/photos/random?client_id=${import.meta.env.VITE_API_KEY}&count=10`; const { data, isLoading, error } = useFetch(url); if (isLoading) returnLoading...; if (error) returnError: {error}; return (...); };
注意:用您的 Unsplash API 访问密钥替换“VITE_API_KEY”环境变量。
如果一切按预期进行,记录数据状态应该会将获取的图像输出到您的终端。

数据准备好后,您就可以开始构建轮播了。首先,导入 `useSnapCarousel` 钩子并解构必要的状态,如下所示:
import { useFetch } from '../hooks/useFetch'; import { useSnapCarousel } from 'react-snap-carousel'; const Carousel = () => { const { scrollRef, pages, goTo, prev, next, activePageIndex, hasPrevPage, hasNextPage, snapPointIndexes, refresh } = useSnapCarousel(); ... return (...); };
创建可滚动的容器
要显示轮播项目,我们需要一个可滚动的容器来呈现项目。我们将 RSC 中的“scrollRef”对象附加到此容器以启用滚动功能:
{data?.map((item, i) => (
))}
在这里,我们还通过获取的图像进行映射并在容器内呈现它们。
请记住,RSC 是无头的,并且它不提供默认样式,所以我们需要定义 CSS 以使轮播看起来并按预期显示。
添加以下 CSS 来设置容器和项目的样式。您可以将其包含在 Carousel 组件的顶层,也可以将其包含在单独的 CSS 文件(例如 Carousel.css)中并导入:
const styles = { container: { position: 'relative', display: 'flex', overflow: 'auto', scrollSnapType: 'x mandatory', scrollBehavior: 'smooth', }, item: { width: '350px', height: '450px', listStyleType: 'none', flexShrink: 0, }, img: { width: '100%', height: '100%', objectFit: 'cover', }, buttonDisabled: {opacity: 0.3}, activeIndex: {opacity: 0.3}, controls: { display: 'flex', justifyContent: 'center', alignItems: 'center', margin: '10px', }, itemSnapPoint: { scrollSnapAlign: 'start', }, };
其他轮播组件尚未添加,因此我们暂时看不到样式效果,但我们可以通过将 `container` 和 `item` 属性分别传递给 `ul` 和 `li` 元素上的 `style` 属性,预先调整项目大小并水平排列:
-
{data?.map((item, i) => (
- ... ))}
在此阶段,你会看到以下内容:

请记住,由于图像尺寸不同,轮播图块的大小可能会有所不同。我们可以使用 CSS 样式来解决这个问题,类似于上面的示例。具体来说,我们将定位“item”和“img”选择器:
item: { width: '350px', height: '450px', listStyleType: 'none', flexShrink: 0, }, img: { width: '100%', height: '100%', objectFit: 'cover', },
这些规则将确保轮播上的每个图块或项目都具有一致的外观,无论其原始图像大小如何。
添加控件
下一步,我们将使用 `next` 和 `prev` 函数向轮播添加控件。在容器元素下方插入以下代码:
根据单击的按钮,将调用任一函数。`String.fromCharCode()` 静态方法将使用提供的 Unicode 代码序列呈现箭头图标。
之前我提到,我们目前使用的 RSC 状态是创建基本轮播所需的唯一条件。但是,如果您此时在浏览器中测试控件,它们将无法按预期工作。
此问题通常由异步数据获取或大图像尺寸引起,这会导致数据加载延迟。在此示例中,图像很大,因此加载需要时间,尽管这几乎不可察觉。因此,RSC 会等到所有数据加载完毕,从而导致控件在延迟期间处于非活动状态。
值得庆幸的是,我们可以通过使用“刷新”功能来解决这个问题,在数据完全加载后更新轮播状态并使控件具有交互性。
在 `Carousel` 组件中添加 `useEffect` 声明。在其中,调用 `refresh` 函数。在依赖项数组中包含 `data` 和 `scrollRef` 状态:
useEffect(() => { refresh(); }, [data, scrollRef]);
现在,如果您返回浏览器并与轮播交互,它应该可以正常运行。

分页
因为我们知道“pages”数组保存了轮播中的项目数量,所以我们可以使用它来创建分页,以便在不同页面的项目之间导航。
我们将在“控件”包装器内的“上一页”和“下一页”按钮之间插入分页:
{pages.map((_, i) => ( ))}
为了使每个分页数字具有交互性并能够滚动到其各自的轮播项,请使用 `goTo` 函数为数字添加点击事件:
... {pages.map((_, i) => ( ))} ...
当单击分页数字时,其索引将作为参数传递给 `goTo` 函数。此索引决定要滚动到的幻灯片。

接下来,我们将使用 `hasPrevPage` 和 `hasNextPage` 状态来检查轮播是位于其内容的开始还是结束,然后相应地禁用 `prev` 或 `next` 按钮:
`buttonDisabled` 样式属性同样会降低按钮的不透明度,使得当任一状态返回真值时,按钮被禁用变得明显。
类似地,我们将使用 `activePageIndex` 状态来确定当前可见的页面,并有条件地设置其样式以突出显示活动页面的指示器:
{pages.map((_, i) => ( ))}
`activeIndex` 样式属性也降低了活动页面的指示器不透明度。
自定义捕捉点
列表中的最后一个是“snapPointIndexes”。我们不需要对它做太多操作,因为它只存储了作为捕捉点(滚动行为停止或捕捉的轮播上的预定义位置)的轮播项目索引。
但是,我们可以使用这些索引来创建其他效果,如条件渲染元素或组件、动态突出显示和自定义导航指示器等。
但是在我们的代码中,我们需要 `snapPointIndexes` 状态是为了设置可滚动容器中各个项目的捕捉行为,如下所示:
{data?.map((item, i) => (
这是“itemSnapPoint”选择器的规则:
itemSnapPoint: { scrollSnapAlign: 'start', },
`scrollSnapAlign`: 'start' 规则指定在发生捕捉时项目的起始边缘(左边缘,对于水平滚动)应与容器的捕捉点对齐。
通过此设置,您现在拥有一个具有完整功能控件和分页的轮播。接下来,我们将研究扩展功能并添加无限滚动。
实现无限滚动功能
无限滚动的概念涉及使轮播在其项目中无缝循环出现。不幸的是,RSC 本身并不支持开箱即用的无限滚动。因此,实现该功能需要一些自定义处理。
自定义处理此功能的一般思路是循环显示内容并控制滚动行为。以下是我们将如何实现此目的的分步方法:
首先,我们将从代码中删除以下项目:
这是因为我们希望控件保持活动状态,即使用户到达轮播的开始或结束。这样,他们就可以尝试滚动到最后一个项目之外。
删除这些项目后,我们可以继续实现自定义处理程序:
const handleNext = useCallback(() => { if (activePageIndex === pages.length - 1) { goTo(0); } else { next(); } }, [activePageIndex, pages.length, goTo, next]); const handlePrev = useCallback(() => { if (activePageIndex === 0) { goTo(pages.length - 1); } else { prev(); } }, [activePageIndex, pages.length, goTo, prev]);
如您所见,我们并没有完全摆脱 `prev` 和 `next` 函数。相反,我们使用 `activePageIndex`、`pages` 和 `goTo` 函数来确定轮播是在开头还是结尾。如果是,我们将顺利滚动回到另一端。否则,我们将像往常一样调用 `prev` 或 `next` 函数。
最后,我们将这些自定义处理程序附加到控制按钮的“onClick”事件:
...
这样,尽管存在限制,我们还是成功地在轮播上创造了无限滚动的幻觉。

以下是 `Carousel` 组件的完整代码:
import {useSnapCarousel} from 'react-snap-carousel'; import {useFetch} from "../hooks/useFetch.js"; import {useEffect, useCallback} from "react"; export const Carousel_test = () => { const url = `https://api.unsplash.com/photos/random?client_id=${ import.meta.env.VITE_API_KEY }&count=20`; const {data, isLoading, error} = useFetch(url); const { scrollRef, pages, goTo, prev, next, activePageIndex, snapPointIndexes, refresh, } = useSnapCarousel(); const handleNext = useCallback(() => { if (activePageIndex === pages.length - 1) { goTo(0); } else { next(); } }, [activePageIndex, pages.length, goTo, next]); const handlePrev = useCallback(() => { if (activePageIndex === 0) { goTo(pages.length - 1); } else { prev(); } }, [activePageIndex, pages.length, goTo, prev]); useEffect(() => { refresh(); }, [data, scrollRef]); if (isLoading) returnLoading...; if (error) returnError: {error}; return (); }; const styles = { container: { position: 'relative', display: 'flex', overflow: 'auto', scrollSnapType: 'x mandatory', scrollBehavior: 'smooth', }, item: { width: '350px', height: '450px', listStyleType: 'none', flexShrink: 0, }, img: { width: '100%', height: '100%', objectFit: 'cover', }, buttonDisabled: {opacity: 0.3}, activeIndex: {opacity: 0.3}, controls: { display: 'flex', justifyContent: 'center', alignItems: 'center', margin: '10px', }, itemSnapPoint: { scrollSnapAlign: 'start', }, };{data?.map((item, i) => (
- ))}
{pages.map((_, i) => ( ))}
结论
React Scroll Carousel 包无疑是创建灵活且可定制的轮播的绝佳选择。鉴于其无头特性,您可以自由地根据特定需求定制轮播的设计。
虽然该软件包有其局限性,但正如我们在文章中看到的那样,扩展其功能的想法并不牵强,并且可以通过扎实的 JavaScript 和 React 知识来实现。
几分钟内即可设置 LogRocket 的现代 React 错误跟踪:
新版本:
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
脚本标记:
Add to your HTML:
3.(可选)安装插件以便与您的堆栈进行更深入的集成:
现在就开始。