react 手写一个hook 实现循环滚动轮播列表

1,316 阅读2分钟

场景

2cb0d2f937743a0e0bdcecfc19c914b.png

如图 粉色底的盒子是可视区域,可视区域内有一组数据,需要实现自动轮播功能;

预期: 自动轮播 假设有9张图片,当滚动到第九张图片时后面接上图片组的第一张,以此实现一个循环滚动的效果

自定义hook

思路:

通过控制maxIndex来操作当前可视区域内的展示的图片的数量;

initIndex初始化的index值;

const prevIndex = index ? index - 1 : maxIndex;来实现向前翻页;

const toNext = () => toIndex(nextIndex);实现向后翻页

import {useState} from 'react';

function useCarousel(maxIndex: number, initIndex: number = 0) {
   if (maxIndex < 0) maxIndex = 0;
   if (initIndex > maxIndex) initIndex = maxIndex;
   const [index, toIndex] = useState(initIndex);
   const prevIndex = index ? index - 1 : maxIndex;
   const toPrev = () => toIndex(prevIndex);
   const nextIndex = index + 1 > maxIndex ? 0 : index + 1;
   const toNext = () => toIndex(nextIndex);
   const indexList = Array.from({length: maxIndex + 1}, (item, index) => index);
   return {prevIndex, toPrev, nextIndex, toNext, index, toIndex, indexList};
}

export default useCarousel;

组件

import useCarousel from '../hooks/useCarousel';
import React, {useEffect, useRef, useState} from 'react';
import clazz from './Demo.module.scss';
type demoProps = {}

const demo: React.FunctionComponent<demoProps> = (props) => {
  // 假设有9组图片数据
   const list = [1,2,3,4,5,6,7,8,9]
   // 初始化maxIndex
   const maxIndex = list.length - 1 + 2;
   const {index, toIndex, toPrev, toNext} = useCarousel(maxIndex, 1);
   
   // 用于控制鼠标hover 停止轮播效果
   const [autoSwitch, setAutoSwitch] = useState(true);
   
   // 轮播位移的效果
   const getTransform = index => `translateX(-${(index + 1) * 220}px)`;
   const ulRef = useRef<HTMLDivElement>();

   const jumpToIndex = (targetIndex: number) => {
      ulRef.current.style.transform = getTransform(targetIndex);
      ulRef.current.style.transition = 'none';
      window.scrollY;
      ulRef.current.style.transition = null;
   };
   
   useEffect(() => {
      // 动画效果为切换到最后一张, 移到头部执行
      if (index === maxIndex) {
         jumpToIndex(0);
         toIndex(1);
      }
      // 动画效果为切换到第一张, 移到末尾执行
      if (index === 0) {
         jumpToIndex(maxIndex);
         toIndex(maxIndex - 1);
      }
   }, [index]);

   useEffect(() => {
      if (autoSwitch) {
         const ti = setTimeout(toNext, 1800);
         return () => clearTimeout(ti);
      }
   }, [toNext, index, autoSwitch]);
   
   return (
      <div className={clazz.box}>
         <div className={clazz.cardList} style={{transform: getTransform(index)}} ref={ulRef}>
            {[...list.slice(-1), ...list, ...list.slice(0, 6)]?.map((item,indexNum) => {
               return <div className={clazz.card}>
                  {item}
               </div>
            })}
         </div>
      </div>

   );
};

export default demo;

CSS

.box {
   margin: 200px auto;
   width: 1100px;
   height: 200px;
   background-color: pink;
   overflow: hidden;
   .cardList {
      display: flex;
   }
   .card {
      height: 200px;
      min-width: 200px;
      background-color: skyblue;
      margin-right: 20px;
      text-align: center;
      line-height: 200px;
      font-size: 20px;
      font-weight: 600;
   }
}