【react】基于umi框架编写轮播图组件

引言

基于该文仿写:web 完整轮播图——带只拖鞋去流浪 https://zhuanlan.zhihu.com/p/138232728

组件源码:https://gitee.com/leftstan/rotation.git

组件效果:https://www.jianguoyun.com/p/Dd81zscQzLDdCRiKuo4E

【react】基于umi框架编写轮播图组件

创建项目

创建一个umi2.x的项目

选择项目类型为app,不使用ts

npm create umi

 

安装依赖

yarn

 

运行项目

yarn start

 

组件调用

data为需要展示图片的数据

size轮播图组件尺寸

delay为播放时延(可选)

const data = [{
  key: "1",
  src: "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/a532e33470d046b3f044d5ea49fc5e9e.png?thumb=1&w=1226&h=460&f=webp&q=90",
  action: "https://www.mi.com"
},
...
]
 
<div>
  {/* 轮播图数据 */}
  {/* 轮播图尺寸, 单位px*/}
  {/* 轮播时延 */}
  <Rotation
     data={data}
     size={{ width: '1200', height: '480' }}
     delay={2000}>
   </Rotation>
</div>

  

 

编写组件

 html布局为三部分内容

图片,导航按钮,上/下一页

        <div
            className={styles.content}
            style={{  size.width + 'px', height: size.height + 'px' }}
            onMouseOver={() => stopPlay()}
            onMouseLeave={() => autoPlay()}
        >
            {/* 图片展示轮播 */}
            <ul className={styles.picContent} >
                {data.map((item) =>
                    <li key={`rotate${item.key}`} style={{  size.width + 'px' }}>
                        <a href={item.action}>
                            <img style={{  size.width + 'px', height: size.height + 'px' }} src={item.src} id={`rotate${item.key}`} />
                        </a>
                    </li>
                )}
            </ul>

            {/* 底部导航按钮跳转 */}
            <ul className={styles.dotButton}>
                {data.map((item, index) =>
                    <li
                        className={`${styles.dot} ${Math.abs(btn) === index ? styles.current : ''}`}
                        key={`btn${index}`} id={`btn${index}`}
                        onClick={() => jump(index)}
                    >
                    </li>
                )}
            </ul>

            {/* 上下页按钮 */}
            <div className={`${styles.btn} ${styles.left}`} onClick={() => {
                prev();
            }}><</div>
            <div className={`${styles.btn} ${styles.right}`} onClick={() => {
                next();
            }}>></div>
        </div>

  

无缝切换

需要在图片【盒子尾部】插入一份第一张图片;

当播放到【最后一张图】(数据最后一张),要跳转到第一张图时,执行动画操作跳转到我们插入到【盒子尾部】的【第一张图片的副本】

此时再播放下一张时,先无动画跳转到【第一张图片】,再执行动画操作跳转到【第二张图片】

使用react hooks定义需要用到的参数

useState进行定义

//图片数据
    const [data, setData] = useState(props.data);
    //图片尺寸
    const [size, setSize] = useState(props.size);
    //图片宽度
    const [width, setWidth] = useState(size.width > 0 ? size.width : 1200);
    //右下角导航按钮当前选项
    const [current, setCurrent] = useState(0);
    const [btn, setBtn] = useState(0);
    //自动播放计时器
    const [timer, setTimer] = useState();

useEffect中进行初始化

useEffect(() => {
        //获取单张图片宽度
        const wid = size.width > 0 ? size.width : 1200;
        setWidth(wid);
        //设置图片盒子宽度
        let pics = document.getElementById('picContent');
        pics.style.width = (data.length + 1) * width + 'px';
        // 将第一张图片 clone 到最后
        let firstLi = pics.firstChild.cloneNode(true);
        pics.appendChild(firstLi);
        //设置自动播放
        autoPlay();
    }, [])

下一页

    const next = () => {
        let pics = document.getElementById('picContent');
        let len = pics.children.length - 1;
        let ind = current;
        //无动画,从尾部跳转到第一张图片
        if (ind >= len) {
            ind = 0;
            pics.style.left = 0;
        }
        ind++;
        //跳转动画
        animate(pics, -width * ind);
        //更新导航按钮
        setCurrent(ind);
        ind >= len ? setBtn(0) : setBtn(ind);
        // console.log("next is: ", ind)
    }

 

底部导航按钮跳转

   const jump = (ind) => {
        let pics = document.getElementById('picContent');
        animate(pics, -width * ind);
        setCurrent(ind);
        setBtn(ind);
    }

动画效果

//动画对象,结束帧位置(目标位置)
    const animate = (obj, target) => {
        clearInterval(obj.timer);
        obj.timer = setInterval(() => {
            var leader = obj.offsetLeft;
            var step = 30;
            //设置不同动画方向
            step = leader < target ? step : -step;
            if (Math.abs(leader - target) >= Math.abs(step)) {
                leader += step;
                obj.style.left = leader + 'px';
            } else {
                obj.style.left = target + 'px';
                clearInterval(obj.timer);
            }
        }, 10)
    }

自动播放

react hooks与setInterval

在react hooks中直接使用setInterval无法达到预期的效果,需要使用useReducer

(具体缘由参考该文:https://www.cnblogs.com/qcloud1001/p/10408634.html)

//设置自动播放
    const autoPlay = () => {
        setTimer(setInterval(() => {
            dispatch('inc');
        }, props.delay));
    }
    //取消自动播放
    const stopPlay = () => {
        clearInterval(timer);
        setTimer(null);
    }
    const [count, dispatch] = useReducer((state, action) => {
        if (action === 'inc') {
            next();
        }
    }, 0);

  

  

参考资料:

https://v2.umijs.org/zh/guide/create-umi-app.html#%E5%88%9B%E5%BB%BA-umi-%E9%A1%B9%E7%9B%AE

https://zhuanlan.zhihu.com/p/138232728

https://www.cnblogs.com/qcloud1001/p/10408634.html