使用 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,它公开了多个状态,可用于管理轮播元素(例如幻灯片、滚动行为和交互)。这些状态包括:

  • pages:这是一个数组,表示轮播中的所有页面或项目组
  • scrollRef:这是一个附加到轮播可滚动容器元素的 ref 对象
  • gotTo:此功能将轮播直接滚动到指定的页面或幻灯片索引
  • prev:滚动到轮播的上一张幻灯片索引的功能
  • next:滚动到轮播的下一个幻灯片索引的功能
  • 刷新:重新计算并刷新轮播状态(例如尺寸、滚动位置、捕捉点)的函数
  • hasPrevPage:一个布尔值,指示是否有可滚动到的上一页
  • hasNextPage:与 hasPrevPage 类似,布尔值表示是否有下一页可以滚动到
  • activePageIndex:当前活动页面的索引
  • snapPointIndexes:表示轮播项的捕捉点的索引数组
  • 乍一看,这个列表似乎太多了,但不用担心。你只需要其中的几个子集——`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) return 
    Loading...
    ; if (error) return
    Error: {error}
    ; return (
    ...
    ); };

    注意:用您的 Unsplash API 访问密钥替换“VITE_API_KEY”环境变量。

    如果一切按预期进行,记录数据状态应该会将获取的图像输出到您的终端。

    Example Of Successful Fetched Image In React Carousel

    数据准备好后,您就可以开始构建轮播了。首先,导入 `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) => ( {item.alt_description} ))}

    在这里,我们还通过获取的图像进行映射并在容器内呈现它们。

    请记住,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) => (
    • ...
    • ))}

    在此阶段,你会看到以下内容:

    Example Of Pictures In React Carousel

    请记住,由于图像尺寸不同,轮播图块的大小可能会有所不同。我们可以使用 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]);

    现在,如果您返回浏览器并与轮播交互,它应该可以正常运行。

    Controls Working In React Carousel

    分页

    因为我们知道“pages”数组保存了轮播中的项目数量,所以我们可以使用它来创建分页,以便在不同页面的项目之间导航。

    我们将在“控件”包装器内的“上一页”和“下一页”按钮之间插入分页:

    {pages.map((_, i) => ( ))}

    为了使每个分页数字具有交互性并能够滚动到其各自的轮播项,请使用 `goTo` 函数为数字添加点击事件:

    ... {pages.map((_, i) => ( ))} ...

    当单击分页数字时,其索引将作为参数传递给 `goTo` 函数。此索引决定要滚动到的幻灯片。

    Carousel scrolling

    接下来,我们将使用 `hasPrevPage` 和 `hasNextPage` 状态来检查轮播是位于其内容的开始还是结束,然后相应地禁用 `prev` 或 `next` 按钮:

    
    
    

    `buttonDisabled` 样式属性同样会降低按钮的不透明度,使得当任一状态返回真值时,按钮被禁用变得明显。

    类似地,我们将使用 `activePageIndex` 状态来确定当前可见的页面,并有条件地设置其样式以突出显示活动页面的指示器:

    {pages.map((_, i) => ( ))}

    `activeIndex` 样式属性也降低了活动页面的指示器不透明度。

    自定义捕捉点

    列表中的最后一个是“snapPointIndexes”。我们不需要对它做太多操作,因为它只存储了作为捕捉点(滚动行为停止或捕捉的轮播上的预定义位置)的轮播项目索引。

    但是,我们可以使用这些索引来创建其他效果,如条件渲染元素或组件、动态突出显示和自定义导航指示器等。

    但是在我们的代码中,我们需要 `snapPointIndexes` 状态是为了设置可滚动容器中各个项目的捕捉行为,如下所示:

    {data?.map((item, i) => (
       
  • ...
  • ))}

    这是“itemSnapPoint”选择器的规则:

    itemSnapPoint: {
       scrollSnapAlign: 'start',
    },

    `scrollSnapAlign`: 'start' 规则指定在发生捕捉时项目的起始边缘(左边缘,对于水平滚动)应与容器的捕捉点对齐。

    通过此设置,您现在拥有一个具有完整功能控件和分页的轮播。接下来,我们将研究扩展功能并添加无限滚动。

    实现无限滚动功能

    无限滚动的概念涉及使轮播在其项目中无缝循环出现。不幸的是,RSC 本身并不支持开箱即用的无限滚动。因此,实现该功能需要一些自定义处理。

    自定义处理此功能的一般思路是循环显示内容并控制滚动行为。以下是我们将如何实现此目的的分步方法:

  • 自定义处理程序:第一步是创建自定义处理程序来代替 prev 和 next 函数。这样,​​我们就可以为我们的实现创建自定义逻辑
  • 监视滚动位置:接下来,我们使用状态来监视滚动何时到达列表的开始或结束
  • 重置滚动位置:最后,当用户浏览列表的最后一项或第一项时,我们会快速回到开头或结尾
  • 首先,我们将从代码中删除以下项目:

  • hasPrevPage 和 hasNextPage 函数
  • 控制按钮上的禁用属性
  • 这是因为我们希望控件保持活动状态,即使用户到达轮播的开始或结束。这样,他们就可以尝试滚动到最后一个项目之外。

    删除这些项目后,我们可以继续实现自定义处理程序:

    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 product in React

    以下是 `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) return 
    Loading...
    ; if (error) return
    Error: {error}
    ; return (
      {data?.map((item, i) => (
    • {item.alt_description}
    • ))}
    {pages.map((_, i) => ( ))}
    ); }; 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', }, };

    结论

    React Scroll Carousel 包无疑是创建灵活且可定制的轮播的绝佳选择。鉴于其无头特性,您可以自由地根据特定需求定制轮播的设计。

    虽然该软件包有其局限性,但正如我们在文章中看到的那样,扩展其功能的想法并不牵强,并且可以通过扎实的 JavaScript 和 React 知识来实现​​。

    几分钟内即可设置 LogRocket 的现代 React 错误跟踪:

  • 访问 https://logrocket.com/signup/ 获取应用程序 ID。
  • 通过 NPM 或脚本标签安装 LogRocket。LogRocket.init() 必须在客户端调用,而不是服务器端。
  • 新版本:

    $ npm i --save logrocket 
    
    // Code:
    
    import LogRocket from 'logrocket'; 
    LogRocket.init('app/id');

    脚本标记:

    Add to your HTML:
    
    
    

    3.(可选)安装插件以便与您的堆栈进行更深入的集成:

  • Redux 中间件
  • ngrx 中间件
  • Vuex 插件
  • 现在就开始。