引言
基于该文仿写:web 完整轮播图——带只拖鞋去流浪 https://zhuanlan.zhihu.com/p/138232728
组件源码:https://gitee.com/leftstan/rotation.git
组件效果:https://www.jianguoyun.com/p/Dd81zscQzLDdCRiKuo4E
创建项目
创建一个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={{ width: size.width + 'px', height: size.height + 'px' }} onm ouseOver={() => stopPlay()} onm ouseLeave={() => autoPlay()} > {/* 图片展示轮播 */} <ul className={styles.picContent} id="picContent"> {data.map((item) => <li key={`rotate${item.key}`} style={{ width: size.width + 'px' }}> <a href={item.action}> <img style={{ width: 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