【Day15】数据展示元件 - Carousel

元件介绍

Carousel 是一个像旋转木马一样会轮流转的轮播元件。在一个内容空间有限的可视范围中进行内容的轮播展示。通常适用於一组图片或是卡片的轮播。

Carousel 的样式也是五花八门,随便在 google 下一个关键字就能够找到各种不同形式的 Carousel。

所以在这边我们也是挑一个简单易做的 Carousel 来实现。

参考设计 & 属性分析

dataSource

由於是一组轮流播放的图片,所以首先我们需要提供给这个元件一个 list 的资料,其中 list 的每一笔资料我们希望能够包含一个 image url。

autoplay

autoplay 提供一个 boolean 来决定这个轮播是否自动切换。

dots

dots 是一个 boolean,用来决定是否出现「指示点」。

介面设计

属性 说明 类型 默认值
className 客制化样式 string
dataSource 轮播资料 list of image url
autoplay 是否自动播放 boolean false
hasDots 是否显示指示点 boolean true
hasControlArrow 是否显示上一个、下一个切换键 boolean true

元件实作

我们的 Carousel 包含了轮播图片本身、左右切换按钮,以及指示点,所以 DOM 大致上会长得像下面这个形状:

<CarouselWrapper>
  <ImageWrapper>
    {...images...}
  </ImageWrapper>
  <ControlButtons />
  <Dots />
</CarouselWrapper>

由下图知道,轮播图片、左右切换按钮以及指示点都是叠在一起的,有点是分不同图层的概念,所以这边的定位都是使用 position: absolute;

图片轮播的计算方法

我们就直接来看 Carousel 的核心,到底要怎麽让图片可以轮播。
这边我们是要做 slide 动画的轮播,所以为了让图片可以在 X 轴上左右滑动,我们势必会使用到 position: absolute;lefttransition 这三个关键的 CSS。

如下图示意,为了让图片能够左右滑动轮播,我们可以先将图片排成一整排,并且在可视范围之外的地方隐藏这些图片。

排成一整排的方式不能像我们以前那样使用 flex 布局来达成,而是会需要算一些数学,计算出每一张图片的位置,也就是他的 left 值,之後要轮转的时候,就是去改变这个 left 数值,搭配 transition 过场动画,就能够做到我们轮播的效果。

概念上已经说明完了,接下来我们来看程序码,left 的计算很简单,就是目前迭代到的这个 image 与正在可视范围中的那个 current image 的距离,乘上图片的宽度就是了:

const makePosition = ({ itemIndex }) => (itemIndex - currentIndex) * imageWidth;

<ImageWrapper>
  {
    dataSource.map((imageUrl, index) => (
      <Image
        key={imageUrl}
        src={imageUrl}
        alt=""
        $left={makePosition({ itemIndex: index })}
      />
    ))
  }
</ImageWrapper>

切换图片的左右按钮

切换左右的按钮我希望只专注在计算哪一张图片是 current ,也就是正在可视范围中的图片,避免在这些 function 里面做太多其他的事,保持他功能的单纯性。

所以看下面 click next 的 function,我只有计算让 current index 不断的 +1,直到尾部的时候再从头开始;反之,click prev 的 function 就是让 current index 不断的 -1,直到 0 的时候再从尾部开始:

const getIndexes = () => {
  const prevIndex = currentIndex - 1 < 0 ? dataSource.length - 1 : currentIndex - 1;
  const nextIndex = (currentIndex + 1) % dataSource.length;

  return {
    prevIndex, nextIndex,
  };
};

const handleClickPrev = () => {
  const { prevIndex } = getIndexes();
  setCurrentIndex(prevIndex);
};

const handleClickNext = useCallback(() => {
  const { nextIndex } = getIndexes();
  setCurrentIndex(nextIndex);
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentIndex]);


<ControlButtons>
  <ArrowLeft onClick={handleClickPrev} />
  <ArrowRight onClick={handleClickNext} />
</ControlButtons>

自动播放

自动播放当然就是要靠我们的 setInterval 了,我们每 3 秒就改变 current index 一次,改变的方式我们就直接呼叫上面做好的 handleClickNext function 就可以了:

useEffect(() => {
  let intervalId;
  if (autoplay) {
    intervalId = setInterval(() => {
      handleClickNext();
    }, 3000);
  }
  return () => {
    clearInterval(intervalId);
  };
}, [autoplay, handleClickNext]);

以上就是我们 Carousel 的重点整理啦!简单展示一下成果:


Carousel 元件原始码:
Source code

Storybook:
Carousel


<<:  [Day 13]每天前进一点应该也是进步吧?(前端篇)

>>:  【PHP Telegram Bot】Day19 - 基础(8):回圈、Xdebug

总结

这个系列开始我们先介绍了 RSS feed 里面的内容和不同平台的格式,也了解到要一次处理这麽多又有...

[Day16] MySQL 简介

之前我们在写 API 程序的时候,一开始使用写死在程序里的资料集合(List),这个方法虽然快速让我...

【Day04】数据输入元件 - Checkbox

元件介绍 Checkbox 是一个多选框元件。通常使用情境是在一个群组的选项当中进行多项选择时使用。...

用React刻自己的投资Dashboard Day28 - 串接台股技术面API

tags: 2021铁人赛 React 上一篇刻好版型後,这篇就可以来串接API拉,目标当然就是让使...

Day 13 「难兄难弟」 单元测试、Code Smell 与重构 - Data Clump 与 Primitive Obsession 篇

图片截自三立新闻 与笔者年纪相当的朋友,肯定还记得小时候有个非常红的电示节目叫「龙兄虎弟」吧。当时...