基于React实现的一个轮播图

2,891 阅读2分钟

基于React实现的一个轮播图

样例

example.gif

前言

  你永远想不到焦急等待放假的时候人能做出什么事情来,比如我就过来写了一篇文章。

  首先,这是我的第一篇,希望也是最后一篇。

  其次,写这篇文章不是为了传播什么知识,毕竟2022年了,这可是人人知道轮播图的时代,我的实现也没有任何有新意的地方。如果除了写本身以外非要赋予一些意义的话,就是我还希望能从诸位身上得到一些在我的认识范围以外的指导。但之所以这样,也是因为我现在懒得去翻一篇篇文章,只想坐等评论指点,要么就无事发生。

  最后,希望你已经放假了。最后的最后,如果你看完了整篇文章,你任何非攻击性的批判都能帮助到我。

实现思路

就像轮播图一样,在末尾补上第一页,移动完成后转换到第一页;在第一页之前补上最后一页,在第一页向前移动时转换到最后一页。

如何使用

      <Carousel
        otherCss={css`
          height: 300px;
        `}
        showNumber={4}
        childSpace="1rem"
      >
        {times(10).map((item) => (
          <div
            key={item}
            css={css`
              background: #ddd;
              color: #fff;
              display: flex;
              align-items: center;
              justify-content: space-around;
            `}
          >
            {item + 1}
          </div>
        ))}
      </Carousel>

参数解释

  • showNumber: 每页轮播图显示的子元素个数
  • childSpace: 组件与组件之间的间隔
  • otherCss: 自定义组件上添加样式的别称,会累加到组件内部的div上
  • children: 展示的内容列表(组件标签里面的)

实现过程

  • 将传入的children转换为数组,并根据每页展示的数量(showNumber)补充最后一页(如果最后一页的个数不足showNumber)以及首尾
  // 规划子元素
  const childrenList = useMemo(() => {
    const childArray = React.Children.toArray(children);
    //最后一页不足showNumber,复制前一页相应的个数补充
    const r = childArray.length % showNumber;
    if (r !== 0) {
      childArray.splice(-r, 0, ...childArray.slice(-showNumber, -r));
    }
    //复制起始的元素补充新的最后一页
    childArray.push(...childArray.slice(0, showNumber));
    //复制末尾的元素(倒数第二页)补充到新的第一页
    childArray.unshift(...childArray.slice(-showNumber * 2, -showNumber));
    return childArray.map((item) => ({ ele: item, _id: uniqueId('_') }));
  }, [children, showNumber]);
  • 计算子卡片宽度
  const childWidth = useMemo(
    () => `calc(${(1 / showNumber) * 100}% - ${childSpace})`,
    [showNumber, childSpace]
  );
  • 左右切换按钮单击
  // 更新left实现轮播效果
  const [targetLeft, setTargetLeft] = useState('-100%');
  const handleBtnClick = useCallback(
    (action) => {
      // action? leftBtn:rightBtn
      let rollWidth = 0;
      const roll = () => {
        const stepRadio = action ? -10 : 10;
        rollWidth += 1;
        if (rollWidth <= 10) {
          setTargetLeft((v) => {
            const curValue = parseInt(v, 10) + stepRadio;
            const pageNumber = childrenList.length / showNumber;
            // 到了最后一页后,回到第一页
            if (curValue <= -(pageNumber - 1) * 100) {
              return '-100%';
            }
            // 到了第一页后,回到倒数第二页(倒数第二页的数据即是最末尾的数据)
            if (curValue === 0) {
              return `-${(pageNumber - 2) * 100}%`;
            }
            return `${curValue}%`;
          });
          requestAnimationFrame(roll);
        }
      };
      requestAnimationFrame(roll);
    },
    [childrenList, showNumber]
  );
  • 组件dom
    <div
      css={css`
        overflow: hidden;
        position: relative;
        padding: ${16 / FONT_BASE}rem 1rem 0;
        ${otherCss}
      `}
    >
      <div
        css={css`
          width: 100%;
          height: 100%;
          position: absolute;
          left: ${targetLeft};
          display: flex;
        `}
      >
        {childrenList.map((item) =>
          React.cloneElement(item.ele as React.ReactElement, {
            style: {
              flexShrink: 0,
              width: childWidth,
              marginLeft: childSpace,
            },
            key: item._id,
          })
        )}
      </div>
      <CarouselButton
        onLeftClick={() => {
          handleBtnClick(false);
        }}
        onRightClick={() => {
          handleBtnClick(true);
        }}
      />
    </div>
  • CarouselButton 就是两个放在左右垂直居中的按钮

总结

第一次写,颇有毕业论文疯狂贴代码的感觉。GGBoy!!! 源码地址(Github)