ReactJs寫旋轉(zhuǎn)木馬輪播圖

備注:最近工作需要凌埂,要用react實(shí)現(xiàn)旋轉(zhuǎn)木馬的輪播圖效果,在網(wǎng)上查了查沒有相似的案例诗芜,只有用react實(shí)現(xiàn)的簡單的輪播圖瞳抓,還有就是用jQuery實(shí)現(xiàn)的旋轉(zhuǎn)木馬輪播圖,在參考了這兩個(gè)實(shí)現(xiàn)方式的基礎(chǔ)上伏恐,我用react轉(zhuǎn)化了這種實(shí)現(xiàn)孩哑,過程真的很糾結(jié),但是還是做出來了翠桦,效果還可以横蜒。

效果圖:

Paste_Image.png

實(shí)現(xiàn)思路分析

1.每張圖片(li節(jié)點(diǎn))的布局

<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函數(shù)在控制他們的排列:

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){
            // 右側(cè)圖片
            Imgscale = Math.pow(scale, (index));
            Imgleft = width - (middleIndex-index)*gap - imgWidth*Imgscale;
            zIndex=middleIndex+1 - index;
            opacity=1/++index;

        }else if(index > middleIndex){
            // 左側(cè)圖片
            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
        }
    }

要想實(shí)現(xiàn)3D的效果,需要同時(shí)控制每張圖片的6個(gè)屬性來回變化销凑,下面分析他們的計(jì)算過程(先布局):

index關(guān)系:

Paste_Image.png

實(shí)際上要分為左右兩部分實(shí)現(xiàn):

右側(cè):

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

圖片的Zindex和opacity需要認(rèn)真考慮一下如何設(shè)置丛晌,其他的都好說,就是數(shù)學(xué)關(guān)系斗幼。
zIndex實(shí)際上最中間的是3澎蛛,向右依次遞減;
opacity中間是1蜕窿,向右依次是1/2,1/3 ... 谋逻;

左側(cè):

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

左側(cè)唯一麻煩的是,opacity的設(shè)置桐经,我是需找的數(shù)學(xué)規(guī)律毁兆,只要讓左側(cè)的opacity呈現(xiàn)1,1/2,1/3 ... 即可。

2.點(diǎn)擊箭頭讓他動(dòng)起來

如果是一般的輪播圖就好辦了阴挣,直接控制ul的left值就可以气堕,不用直接操作dom,而這種輪播圖是不斷控制每一個(gè)li,讓他的狀態(tài)變化到上一個(gè)或者下一個(gè)li的狀態(tài)送巡,用state控制變量的方式實(shí)在是不會(huì),所以還是操作的dom盒卸。

首先骗爆,組件掛載后,獲取dom對(duì)象蔽介,組成數(shù)組:

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

然后摘投,比如點(diǎn)擊左側(cè)按鈕的時(shí)候,遍歷dom數(shù)組虹蓄,讓當(dāng)前的li的狀態(tài)變?yōu)樗纳弦粋€(gè)(prev)li的狀態(tài)犀呼,處理一下臨界的問題。

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這個(gè)函數(shù)是控制他運(yùn)動(dòng)的薇组,

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是封裝的緩動(dòng)函數(shù)外臂,這個(gè)不重要,就不詳細(xì)講了律胀。

點(diǎn)擊右側(cè)按鈕的時(shí)候原理類似宋光,就不再贅述。

3.自己動(dòng)起來

這個(gè)就沒啥可說的了炭菌,就是設(shè)置定時(shí)器不斷觸發(fā)右擊箭頭函數(shù)罪佳,鼠標(biāo)移入清除定時(shí)器,鼠標(biāo)移出黑低,開啟定時(shí)器即可赘艳。

4.小圓點(diǎn)跟著點(diǎn)亮

維護(hù)一個(gè)全局的state變量,activeIndex克握,每次dom運(yùn)動(dòng)的話就會(huì)變化這個(gè)值蕾管,然后控制點(diǎn)是否點(diǎn)亮。

5點(diǎn)擊某個(gè)小圓點(diǎn)菩暗,讓他運(yùn)動(dòng)到當(dāng)前位置

這是難點(diǎn)娇掏!

代碼如下:

// 點(diǎn)擊小圓點(diǎn)動(dòng)
    gotoDotView() {
        if(this.state.dotsIndex == this.state.activeIndex){
            return ;
        }else{
            let len = this.itemsArr.length;
            // 運(yùn)動(dòng)到小圓點(diǎn)指示的位置
            if(this.state.dotsIndex - this.state.activeIndex > 0){
                // 如果點(diǎn)擊在右側(cè) 向左運(yùn)動(dòng)
                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{
                // 如果點(diǎn)擊在左側(cè)
                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);
                })
            }
        }
    }

這里要分為兩種情況,點(diǎn)擊點(diǎn)在當(dāng)前活動(dòng)點(diǎn)的右側(cè)勋眯,或者左側(cè)婴梧。然后記錄當(dāng)前兩個(gè)點(diǎn)之間的差值,這個(gè)時(shí)候客蹋,遍歷每個(gè)dom塞蹭,當(dāng)前的item要變?yōu)橛?jì)算完差值后的item的狀態(tài),并且考慮臨界值的處理讶坯,我也說不清楚番电,具體還是看代碼吧。

6.關(guān)于緩動(dòng)函數(shù)

看代碼:

/*
     * animate函數(shù)是動(dòng)畫封裝函數(shù)
     * @para0  elem參數(shù)就是運(yùn)動(dòng)的對(duì)象
     * @para1  targetJSON參數(shù)就是運(yùn)動(dòng)的終點(diǎn)狀態(tài),可以寫px漱办,也可以不寫px
     * @para2  time是運(yùn)動(dòng)總時(shí)間这刷,毫秒為單位
     * @para3  tweenString緩沖描述詞,比如"Linear"
     * @para4  callback是回調(diào)函數(shù)娩井,可選
    */
    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;
        //初始狀態(tài)暇屋,放在origninalJSON里面
        let originalJSON = {};
        //變化的多少,放在deltaJSON里面
        let deltaJSON = {};

        for(let k in targetJSON){
            originalJSON[k] = parseFloat(elem.style[k]);
            //把每個(gè)targetJSON中的值都去掉px
            targetJSON[k] = parseFloat(targetJSON[k]);
            //變化量JSON
            deltaJSON[k] = targetJSON[k] - originalJSON[k];
        }

        //總執(zhí)行函數(shù)次數(shù):
        let maxFrameNumber = time / interval;
        //當(dāng)前幀編號(hào)
        let frameNumber = 0;
        //這是一個(gè)臨時(shí)變量一會(huì)兒用  
        let tween;
        //定時(shí)器
        let timer = setInterval(() => {
            //要讓所有的屬性發(fā)生變化
            for(let k in originalJSON){
                // tween就表示這一幀應(yīng)該在的位置:
                tween = Tween[tweenString](frameNumber , originalJSON[k] , deltaJSON[k] , maxFrameNumber);
                //根據(jù)是不是opacity來設(shè)置單位
                if(k != "opacity"){
                    elem.style[k] = tween + "px";
                }else{
                    elem.style[k] = tween;
                }
            }

            //計(jì)數(shù)器
            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);
                //拿掉是否在動(dòng)屬性洞辣,設(shè)為false
                callback && callback();
            }
        },interval);
    }

實(shí)際上這個(gè)封裝也不難咐刨,主要在Tween的理解上,每個(gè)緩動(dòng)函數(shù)接收四個(gè)參數(shù)扬霜,分別為:當(dāng)前幀編號(hào)定鸟,初始值,結(jié)束值著瓶,結(jié)束幀編號(hào)联予。

在一個(gè)就是注意opacity和zIndex要淡出處理一下就可以了。

最后說一下材原,這個(gè)輪播是可以根據(jù)實(shí)際情況進(jìn)行個(gè)化配置躯泰,

lunboObject: {
        "width":995,//幻燈片的寬度
        "height":335,//幻燈片的高度
        "imgWidth":690,//幻燈片第一幀的寬度
        "interval": 2000,//幻燈片滾動(dòng)的間隔時(shí)間
        "scale":0.85, //記錄顯示比例關(guān)系
        "number":5,
        "autoPlay":true,
        "vertical":"top",  // center或者bottom,居中對(duì)齊或底部對(duì)齊
        "tweenString":"QuadEaseIn" // 運(yùn)動(dòng)方式,緩沖曲線
    }

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末华糖,一起剝皮案震驚了整個(gè)濱河市麦向,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌客叉,老刑警劉巖诵竭,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兼搏,居然都是意外死亡卵慰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門佛呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裳朋,“玉大人,你說我怎么就攤上這事吓著±鸬眨” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵绑莺,是天一觀的道長暖眼。 經(jīng)常有香客問我,道長纺裁,這世上最難降的妖魔是什么诫肠? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任司澎,我火速辦了婚禮,結(jié)果婚禮上栋豫,老公的妹妹穿的比我還像新娘挤安。我一直安慰自己,他們只是感情好丧鸯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布蛤铜。 她就那樣靜靜地躺著,像睡著了一般骡送。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上絮记,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天摔踱,我揣著相機(jī)與錄音,去河邊找鬼怨愤。 笑死派敷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撰洗。 我是一名探鬼主播篮愉,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼差导!你這毒婦竟也來了试躏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤设褐,失蹤者是張志新(化名)和其女友劉穎颠蕴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體助析,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犀被,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了外冀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寡键。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雪隧,靈堂內(nèi)的尸體忽然破棺而出西轩,到底是詐尸還是另有隱情,我是刑警寧澤脑沿,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布遭商,位于F島的核電站,受9級(jí)特大地震影響捅伤,放射性物質(zhì)發(fā)生泄漏劫流。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祠汇。 院中可真熱鬧仍秤,春花似錦、人聲如沸可很。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽我抠。三九已至苇本,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菜拓,已是汗流浹背瓣窄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纳鼎,地道東北人俺夕。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像贱鄙,于是被迫代替她去往敵國和親劝贸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議逗宁。它實(shí)...
    香橙柚子閱讀 23,733評(píng)論 8 183
  • 她房間抽屜是大開著的映九。 她的紅領(lǐng)巾從床上垂下來半截。 她的桌子一片狼藉瞎颗。 她的臺(tái)燈沒關(guān)氯迂。 她吃過的零食包裝袋在客廳...
    諾拉的以后閱讀 168評(píng)論 0 0
  • 窗沿上突然冒出個(gè)腦袋,這不是路口小賣店店主的兒子嘛言缤,我開門嚼蚀,他拖著步子走進(jìn)來,只見他身穿棉布襖管挟,腦袋上扣頂狗皮帽轿曙,...
    虹霓閱讀 400評(píng)論 2 3
  • 項(xiàng)目需要 CAS配置后只能配置一個(gè)地址 要么外網(wǎng)要么內(nèi)網(wǎng) 很不方便,也不能滿足項(xiàng)目需要 ,所以對(duì)項(xiàng)目做了相應(yīng)的調(diào)整...
    BetterFuture閱讀 1,427評(píng)論 0 2