美文网首页React.js学习React
ReactJs写旋转木马轮播图

ReactJs写旋转木马轮播图

作者: 随遇而安_2750 | 来源:发表于2017-06-29 16:59 被阅读119次

    备注:最近工作需要,要用react实现旋转木马的轮播图效果,在网上查了查没有相似的案例,只有用react实现的简单的轮播图,还有就是用jQuery实现的旋转木马轮播图,在参考了这两个实现方式的基础上,我用react转化了这种实现,过程真的很纠结,但是还是做出来了,效果还可以。

    效果图:

    Paste_Image.png

    实现思路分析

    1.每张图片(li节点)的布局

    <ul className={style['poster-list']} style={{width:width,height:height}}>
       {
            this.props.imgArray.map(function(item,index){
                return <li ref={'items'+index} className={style['poster-item']} style={this.renderstyle(index)} key={index}><a href={this.props.linkArray[index]}><img width="100%" height="100%" src={item}/></a></li>;
            }.bind(this))
       }
    </ul>
    

    主要就是renderstyle函数在控制他们的排列:

    renderstyle(index) {
            const { number, width, imgWidth, scale, vertical, height } = this.props.lunboObject;
            const middleIndex = Math.floor(number / 2);
            const btnWidth = (width-imgWidth) / 2;
            const gap = btnWidth/middleIndex;
            let Imgleft;
            let ImgTop;
            let Imgscale;
            let zIndex;
            let opacity;
    
            if(index <= middleIndex){
                // 右侧图片
                Imgscale = Math.pow(scale, (index));
                Imgleft = width - (middleIndex-index)*gap - imgWidth*Imgscale;
                zIndex=middleIndex+1 - index;
                opacity=1/++index;
    
            }else if(index > middleIndex){
                // 左侧图片
                Imgscale = Math.pow(scale, (number-index));
                Imgleft = (index-(middleIndex+1))*gap;
                zIndex = index-middleIndex;
                opacity = 1 - middleIndex/index;
            }
    
            switch(vertical){
                case 'bottom':
                    ImgTop = parseInt(height - height*Imgscale);
                break;
                case 'center':
                    ImgTop = parseInt((height - height*Imgscale)/2);
                break;
                default:
                    ImgTop = parseInt((height - height*Imgscale)/2);
            }
    
            return {
                width: parseInt(imgWidth*Imgscale),
                height: parseInt(height*Imgscale), 
                left:parseInt(Imgleft),
                zIndex:zIndex,
                opacity:opacity,
                top:ImgTop
            }
        }
    

    要想实现3D的效果,需要同时控制每张图片的6个属性来回变化,下面分析他们的计算过程(先布局):

    index关系:

    Paste_Image.png

    实际上要分为左右两部分实现:

    右侧:

    Imgscale = Math.pow(scale, (index));
    Imgleft = width - (middleIndex-index)*gap - imgWidth*Imgscale;
    zIndex=middleIndex+1 - index;
    opacity=1/++index;
    

    图片的Zindex和opacity需要认真考虑一下如何设置,其他的都好说,就是数学关系。
    zIndex实际上最中间的是3,向右依次递减;
    opacity中间是1,向右依次是1/2,1/3 ... ;

    左侧:

    // 左侧图片
    Imgscale = Math.pow(scale, (number-index));
    Imgleft = (index-(middleIndex+1))*gap;
    zIndex = index-middleIndex;
    opacity = 1 - middleIndex/index;
    

    左侧唯一麻烦的是,opacity的设置,我是需找的数学规律,只要让左侧的opacity呈现1,1/2,1/3 ... 即可。

    2.点击箭头让他动起来

    如果是一般的轮播图就好办了,直接控制ul的left值就可以,不用直接操作dom,而这种轮播图是不断控制每一个li,让他的状态变化到上一个或者下一个li的状态,用state控制变量的方式实在是不会,所以还是操作的dom。

    首先,组件挂载后,获取dom对象,组成数组:

    componentDidMount() {
           for(let i=0;i<this.props.lunboObject.number;i++){
                this.itemsArr.push(findDOMNode(this.refs['items'+(i)]));
           };
            this.autoPlay();
        }
    

    然后,比如点击左侧按钮的时候,遍历dom数组,让当前的li的状态变为他的上一个(prev)li的状态,处理一下临界的问题。

    this.itemsArr.forEach((item, index) => {
        let self = item;
        let next = this.itemsArr[index+1];
        if(index == (len-1)){
            next = this.itemsArr[0];
        }
        
        this.rotateStyle(self, next);
    })
    

    rotateStyle这个函数是控制他运动的,

    rotateStyle(self, next) {
            const { left, top, width, height, zIndex, opacity } = next.style;
            this.animate(self, {left:left,width:width,height:height,zIndex:zIndex,opacity: opacity,top:top}, this.props.lunboObject.tweenString, () => {
                ++this.LOOPNUM ;
            });
        }
    

    animate是封装的缓动函数,这个不重要,就不详细讲了。

    点击右侧按钮的时候原理类似,就不再赘述。

    3.自己动起来

    这个就没啥可说的了,就是设置定时器不断触发右击箭头函数,鼠标移入清除定时器,鼠标移出,开启定时器即可。

    4.小圆点跟着点亮

    维护一个全局的state变量,activeIndex,每次dom运动的话就会变化这个值,然后控制点是否点亮。

    5点击某个小圆点,让他运动到当前位置

    这是难点!

    代码如下:

    // 点击小圆点动
        gotoDotView() {
            if(this.state.dotsIndex == this.state.activeIndex){
                return ;
            }else{
                let len = this.itemsArr.length;
                // 运动到小圆点指示的位置
                if(this.state.dotsIndex - this.state.activeIndex > 0){
                    // 如果点击在右侧 向左运动
                    const dotsDiff = this.state.dotsIndex - this.state.activeIndex;
                    this.setState({
                        activeIndex: this.state.activeIndex + dotsDiff
                    })
                    
                    this.itemsArr.forEach((item, index) => {
                        let self = item;
                        let nextIndex = Number.parseInt(index-dotsDiff);
                        if(nextIndex < 0){
                            nextIndex = nextIndex+len;
                        }
                        let next = this.itemsArr[nextIndex];
                        this.rotateStyle(self, next);
                    })
                }else{
                    // 如果点击在左侧
                    const dotsDiff = this.state.activeIndex - this.state.dotsIndex;
                    this.setState({
                        activeIndex: this.state.activeIndex - dotsDiff
                    })
    
                    this.itemsArr.forEach((item, index) => {
                        let self = item;
                        let prevIndex = Number.parseInt(index+dotsDiff);
                        if(prevIndex >= len){
                            prevIndex = prevIndex-len;
                        }
                        
                        let prev = this.itemsArr[prevIndex];
                        this.rotateStyle(self, prev);
                    })
                }
            }
        }
    

    这里要分为两种情况,点击点在当前活动点的右侧,或者左侧。然后记录当前两个点之间的差值,这个时候,遍历每个dom,当前的item要变为计算完差值后的item的状态,并且考虑临界值的处理,我也说不清楚,具体还是看代码吧。

    6.关于缓动函数

    看代码:

    /*
         * animate函数是动画封装函数
         * @para0  elem参数就是运动的对象
         * @para1  targetJSON参数就是运动的终点状态,可以写px,也可以不写px
         * @para2  time是运动总时间,毫秒为单位
         * @para3  tweenString缓冲描述词,比如"Linear"
         * @para4  callback是回调函数,可选
        */
        animate(elem , targetJSON , tweenString , callback){
            // 缓冲描述词集合
            const Tween = { 
                Linear: (t, b, c, d) => {
                    return c * t / d + b;
                },
                //二次的
                QuadEaseIn: (t, b, c, d) => {
                    return c * (t /= d) * t + b;
                },
                QuadEaseOut: (t, b, c, d) => {
                    return -c * (t /= d) * (t - 2) + b;
                },
                QuadEaseInOut: (t, b, c, d) => {
                    if ((t /= d / 2) < 1) return c / 2 * t * t + b;
                    return -c / 2 * ((--t) * (t - 2) - 1) + b;
                },
                //三次的
                CubicEaseIn: (t, b, c, d) => {
                    return c * (t /= d) * t * t + b;
                },
                CubicEaseOut: (t, b, c, d) => {
                    return c * ((t = t / d - 1) * t * t + 1) + b;
                },
                CubicEaseInOut: (t, b, c, d) => {
                    if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
                    return c / 2 * ((t -= 2) * t * t + 2) + b;
                },
                //四次的
                QuartEaseIn: (t, b, c, d) => {
                    return c * (t /= d) * t * t * t + b;
                },
                QuartEaseOut: (t, b, c, d) => {
                    return -c * ((t = t / d - 1) * t * t * t - 1) + b;
                },
                QuartEaseInOut: (t, b, c, d) => {
                    if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
                    return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
                }
            };
           
            let interval = 15;
            let time = 300;
            //初始状态,放在origninalJSON里面
            let originalJSON = {};
            //变化的多少,放在deltaJSON里面
            let deltaJSON = {};
    
            for(let k in targetJSON){
                originalJSON[k] = parseFloat(elem.style[k]);
                //把每个targetJSON中的值都去掉px
                targetJSON[k] = parseFloat(targetJSON[k]);
                //变化量JSON
                deltaJSON[k] = targetJSON[k] - originalJSON[k];
            }
    
            //总执行函数次数:
            let maxFrameNumber = time / interval;
            //当前帧编号
            let frameNumber = 0;
            //这是一个临时变量一会儿用  
            let tween;
            //定时器
            let timer = setInterval(() => {
                //要让所有的属性发生变化
                for(let k in originalJSON){
                    // tween就表示这一帧应该在的位置:
                    tween = Tween[tweenString](frameNumber , originalJSON[k] , deltaJSON[k] , maxFrameNumber);
                    //根据是不是opacity来设置单位
                    if(k != "opacity"){
                        elem.style[k] = tween + "px";
                    }else{
                        elem.style[k] = tween;
                    }
                }
    
                //计数器
                frameNumber++;
                if(frameNumber == maxFrameNumber){
                    for(let k in targetJSON){
                        if(k == "opacity" || k == "zIndex"){
                            elem.style[k] = targetJSON[k];
                        }else{
                            elem.style[k] = targetJSON[k] + "px";
                        }
                    }
                    clearInterval(timer);
                    //拿掉是否在动属性,设为false
                    callback && callback();
                }
            },interval);
        }
    

    实际上这个封装也不难,主要在Tween的理解上,每个缓动函数接收四个参数,分别为:当前帧编号,初始值,结束值,结束帧编号。

    在一个就是注意opacity和zIndex要淡出处理一下就可以了。

    最后说一下,这个轮播是可以根据实际情况进行个化配置,

    lunboObject: {
            "width":995,//幻灯片的宽度
            "height":335,//幻灯片的高度
            "imgWidth":690,//幻灯片第一帧的宽度
            "interval": 2000,//幻灯片滚动的间隔时间
            "scale":0.85, //记录显示比例关系
            "number":5,
            "autoPlay":true,
            "vertical":"top",  // center或者bottom,居中对齐或底部对齐
            "tweenString":"QuadEaseIn" // 运动方式,缓冲曲线
        }
    

    github地址:https://github.com/GeWeidong/react-lunbo

    相关文章

      网友评论

      • 秋天森林里的松鼠:你好大神,这个旋转轮播好像number是多少就只能展示多少张图,而且是全部展示出来,实现不了展示区展示number张图 然后轮流把LunboConfig.imgArr的所有图片都展示出来。

      本文标题:ReactJs写旋转木马轮播图

      本文链接:https://www.haomeiwen.com/subject/ghtjqxtx.html