ReactJs写旋转木马轮播图

备注:最近工作需要,要用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容