react 组件无缝轮播
安稳. 人气:0正文
需求是做一个无缝轮播图,我说这不是有很多现成的轮子吗?后来了解到他有一个特殊的需求,他要求小圆点需要在轮播图外面,因为现在大部分插件都是将小圆点写在轮播图内部的,这对于不了解插件内部结构的小伙伴确实不知道如何修改。
很久没有写插件的我准备写一个插件(react)
无缝轮播
无缝轮播从最后一张到第一张的过程中不会原路返回,它就像轮子似的,从结束到开始是无缝连接的,非常自然地循环下去。
实现思路
轮播图的实现思路有很多,我们这里采用的是最简单的轮播图方案,如上图,即当轮播图轮播到第x张图片时,当前整个轮播图列表中只保留第x张图片,其余图片dom全部隐藏掉即可。
那么大家有一个疑问,这样不会导致切换时不连贯吗?这个大家不必担心,我们可以在上一个轮播图小时和下一个轮播图展现时增加动画效果,来达到连贯的感觉。
构思使用时代码结构
参考了大部分轮播图组件,得出来下面的这种使用结构。
import Carousel,{ Item } from '组件' render(){ return ( <Carousel> <Item><img src="xxx" /></Item> <Item><img src="xxx" /></Item> <Item><img src="xxx" /></Item> </Carousel> ) }
Carousel组件
新建Carousel组件,这个组件是组件的整体框架文件。
内部增加inner div 来充当当前展示轮播图的视口
const Carousel=()=>{ return ( <div className={'carousel'}> <div className={'carousel-inner'}> {xxx轮播图xxx} </div> </div> ) }
overflow:hidden是关键
.inner{ width:100%; height:100%; position: relative; overflow: hidden; }
CarouselItem组件
新建CarouselItem组件,这个组件是Carousel组件的子组件,是轮播图列表每一项的容器。
const CarouselItem=(props)=>{ return ( <div className={'carousel-item'}> {props.children} </div> ) }
注意 这里需要使用top0 left0 不然显示的时候会从上往上偏移 导致错误显示
.carousel-item{ position: absolute; left:0; top:0; width: 100%; height:100%; }
完善组件
- 如何显示当前轮播图元素
之前我们说过这次我们轮播图的核心思路是显示当前元素,那么什么情况下显示当前元素呢?
carousel组件内有一个state表示当前轮播到第几张图 我们这里叫current,而根据传入carousel的元素排序,我们可以在item内部拿到当前是第几张图片,然后2者如果是相等的,就显示当图片。
我们如何获取元素当前的index呢?我们可以利用react提供的解析children的api
// util import React from "react"; export function injecteIndex(children:React.ElementType,current):any{ let result:React.ReactElement[]=[]; React.Children.forEach(children,(item,index)=>{ result.push(React.cloneElement((item as any),{ //selfIndex即为轮播图的index selfIndex:index, key:index, current })) }); return result; } //carousel组件 //initial 为传入配置 默认为0 即默认展示第一张图 const Carousel=()=>{ const { initial }=props; const [current,setCurrent]=useState<number>(initial); return ( <div className={'carousel'}> <div className={'carousel-inner'}> {injecteIndex(children,current)} </div> </div> ) } //carousel-item组件 const CarouselItem=(props)=>{ const { selfIndex,current }=props; const visible=selfIndex===current return ( {visible && <div className={'carousel-item'}> {props.children} </div>} ) }
- 实现autoPlay
上面我们其实已经实现了第一张图的展示,那么我们如何让他动起来呢?其实也很简单,我们一起来实现一下
//useLatest import { useEffect, useRef } from "react" export default (params:any)=>{ const latest=useRef(); useEffect(()=>{ latest.current=params; }) return latest; } //carousel组件 const Carousel=(props)=>{ const { //initial 为传入配置 默认为0 即默认展示第一张图 initial=0, //是否自动轮播 默认为是 autoplay=true, //时间间隔 默认为3秒 interval=3000 }=props; //新建定时器存储变量 const timer:any=useRef(null); const [current,setCurrent]=useState<number>(initial); const [itemLength]=useState<number>(React.Children.count(children)); //解决闭包问题 const latest:any=useLatest(current); const autoPlay=()=>{ //设置定时器 每隔3s切换到下一张图片 timer.current=setInterval(()=>{ setStep('next'); },interval); } const setStep=(direction:'prev'|'next')=>{ switch(direction){ case 'prev': let prevStep:number=latest.current-1; //当为第一张时 跳到第5张 if(prevStep===-1){ prevStep=itemLength-1; } setCurrent(prevStep); break; case 'next': let nextStep:number=latest.current+1; //当为最后一张时 跳到第1张 if(nextStep===itemLength){ nextStep=0; } setCurrent(nextStep); break; default: } } useEffect(()=>{ if(autoplay){ //自动轮播 autoPlay(); } return ()=>{ //销毁定时器 clearInterval(timer.current); timer.current=null; } },[autoplay]); return ( <div className={'carousel'}> <div className={'carousel-inner'}> {injecteIndex(children,current)} </div> </div> ) } ```# 完成动画 其实上面我们已经把无缝轮播业务逻辑完成了,那么如何让他轮播起来呢?我们这里使用react官方推荐的一个过渡库,因为react并没有像vue为我们提供transition api。 ```js const CarouselItem=(props)=>{ const { children, selfIndex }=props; const visible=selfIndex===current; return ( <CSSTransition mountOnEnter unmountOnExit appear in={visible} timeout={500} classNames={'carousel'}> <div className={'carousel-item'} > {children} </div> </CSSTransition> ) });
.carousel-enter-active,.carousel-exit-active{ transition: all 500ms linear; } .carousel-enter{ transform: translateX(100%); opacity: 0.8; } .carousel-enter-active{ transform: translateX(0); opacity: 1; } .carousel-exit{ transform: translateX(0); opacity: 1; } .carousel-exit-active{ transform: translateX(-100%); opacity: 0.8; }
之前我们设置的top:0 left:0 可以得出轮播元素的原点都是容器的左上角 在enter过程将translateX从100到0 即可展示从右往左的动画效果 在exit过程前将translateX从0到-100 即可展示从左往右的动画效果
完成小圆点
其实让我写轮播图的老兄 最大的难点是小圆点的位置 因为大部分轮播图 小圆点部分都是写在inner里面的 而inner元素为overflow:hidden,即不管如何小圆点,他都会溢出隐藏。所以这次我们把小圆点放在inner元素上,并且提供一个属性让小圆点可调。
//carousel <div className={classes}> <div className={'inner'}> {injecteIndex(children)} </div> <Dots length={itemLength} position={dotPosition}/> </div> //dots原点 <div className={classnames( classes, )} style={{...position}}> { newArray.map((item,index)=>( <div className={classnames(`${prefixCls}-item`,{ 'active':current===index })} key={index} onClick={()=>onClick(index)}/> )) } </div>
实现效果
源码地址
如果大家有想使用的话 可以放心 使用 源码已发到github和npm上面
npm install @parrotjs/carousel -S
加载全部内容