亲宝软件园·资讯

展开

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>}
    )
}

上面我们其实已经实现了第一张图的展示,那么我们如何让他动起来呢?其实也很简单,我们一起来实现一下

//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

加载全部内容

相关教程
猜你喜欢
用户评论