react中實現數據驅動動畫實現

react實現數據驅動動畫的難點和痛點

  • 如果只是單純的實現動畫,不需要與數據交互,css可以很好實現一些簡單的不需要重複執行的動畫(解釋一下這裏所說的不用重複執行的動畫是指不需要更改dom節點在html文檔重的結構順序),並且react也提供了很多動畫庫,比如react-motion,animejs等,問題是如果動畫的驅動需要由數據驅動,並且不同的數據要採用不同的動畫,動畫執行完畢之後需要調整dom節點在html文檔中的結構順序,也就是說要在componentWillReceiveProps這個生命週期裏處理所有的邏輯操作。
  • 數據推送過快會出現“供不應求“,處理不好動畫的執行和數據的改變會出現不同步的問題。編程模式裏,有生產者和消費者模式可以解決這個問題。
    讀者可能會覺得哪有這樣的業務需求呢,且看下文細說。

業務需求

業務需求是這樣的,如下圖在網頁左側有7個div,後臺會每隔幾分鐘推送一次數字,當推送的數字在左側的div裏時,就會一次向上面/下面的div交換位置,直至交換到中間位置。例如,當後臺推送數據1時,1就會與2這個div交換位置,此時1就會到達2的位置,2到達了原來1的位置,然後1再與3交換位置,然後再與4交換位置。如果推送的是7就一次往上交換位置,直至到4的位置。如果後臺推送的數字已經在中間的位置了,就什麼不用處理。但是,如果後臺推送的數據不在左側的任何一個div中,就從後臺獲取新的7條數據替換左側的7個div裏的數據。
業務需求的圖片演示

具體的實現細節

defaultData是一個數組,存放7個div中數據,div都有各自的

  <div id='pagParent' className={styles.parent} style={{ width: '12.5vw', height: "100vh", }}>
{
 	defaultData.length > 0 ? defaultData.map((e, index) => 
 	{
         return <div id={`div${index}`} key={index} style={{ color: index === 3 ? "rgb(228,94,35)" : "rgb(255,255,255)" }} >
          <Icon type="pie-chart" />
          <p style={{ fontSize: "1.5em" }}>{e.logicEntityGroupName}</p></div>
     }) : <></>
}

</div>

這是控制7個div樣式的,這個設計也是有技巧的,一開始的時候我是把7個div每個div都單獨設置一個class,然後在數據遍歷的時候將一次賦予class,這樣就出現了一個嚴重的問題,react需要首先引入css或者less文件,在通過遍歷數據生成div的同時需要將對應的class放在div裏,當時我是這樣解決的,我在less樣式文件裏定義了7個div的樣式,分別是div1,div2,div3,div4,div5,div6,div7這樣就可以通過ES6模版字符串拼接生成樣式類名了,${styles.div}${index},這樣寫有問題嗎?直到在github上的一個大神給我了提示說:**styles是從less注入進去的,這麼寫babel支持不了。最好不要這樣用css,沒法維護的如果非要這麼寫可以用:global。className = { chanxian${i}}**爲了給這個7個div設置不同的的樣式,我想到了css僞類選擇器,雖然這樣寫執行效率很低,但是當時我似乎沒有找到更好的解決方法。以下是css樣式代碼

.parent{
    position: absolute;
    width: 12.5vw;
    height: 100vh;
    div{
      width: 200px;
      height: 100px;
      background-image: url(../../assets//title1.jpg);
      background-position: center;
      position: fixed;
      background-size: 100% 60%;
      zoom:0.9;
      background-repeat: no-repeat;
      display: flex;
      color:@divColor;
      i{
        font-size: 3em;
        color: rgb(9,109,217);
        padding-top: 17% ;
        padding-left:6%;
      }
      p{
        font-size: 20px;
        margin-top: 18%;
        margin-left: 7%;
        color: inherit;
      }
    }
    div:nth-child(1){
    left:0px;
    top:190px;
    }
    div:nth-child(2){
    left:14px;
    top:268px;
    }
    div:nth-child(3){
      left:25px;
    top:346px;
    }
    div:nth-child(4){
      left:40px;
    top:424px;
    }
    div:nth-child(5){
      left:34px;
      top:502px;
    }
    div:nth-child(6){
      left:22px;
    top:580px;
    }
    div:nth-child(7){
      left:0px;
    top:660px;
    } 
  }

動畫實現部分
由於每一個div都不是相同的動畫機制,所以需要爲每一個div書寫不同的動畫執行函數,這裏我引入了animejs這個動畫庫,我敢保證有更好的辦法來找到這6個動畫的共性,抽離出共性然後在根據不同的參數執行函數,我覺得這樣更容易維護。出於技術點和沒有找到共性的原因,我採用了下面愚笨的方法,如果讀者有更好的方法實現,請一定告訴我

  //  觸發第一個div
    onClickTo0 = (tempstyle) => anime({
        targets: '#div0',
        top: tempstyle[1].top,//1
        left: tempstyle[1].left,
        easing: 'easeInOutCirc',
        begin: function (anim) {
            anime({
                targets: "#div1",
                top: tempstyle[0].top,//0
                left: tempstyle[0].left,
                easing: 'easeInOutCirc',
                duration: 450,
            })
        },
        complete: () => {
            var father = document.getElementById("pagParent").children;
            let p = document.getElementById("pagParent");
            father[0].id = "div1";
            father[1].id = "div0";
            let existingnode1 = father[1];
            let existingnode0 = father[0];
            p.insertBefore(existingnode1, existingnode0);
            anime({
                targets: "#div1",
                top: tempstyle[2].top,//2
                left: tempstyle[2].left,
                easing: 'easeInOutCirc',
                duration: 450,
                begin: function (anim) {
                    anime({
                        targets: "#div2",
                        top: tempstyle[1].top,//1
                        left: tempstyle[1].left,
                        easing: 'easeInOutCirc',
                        duration: 450,
                    })
                },
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[1].id = "div2";
                    father[2].id = "div1";
                    let existingnode2 = father[2];
                    let existingnode1 = father[1];
                    p.insertBefore(existingnode2, existingnode1);
                    anime({
                        targets: "#div2",
                        top: tempstyle[3].top,//3
                        left: tempstyle[3].left,
                        color: "rgb(228,94,35)",
                        easing: 'easeInOutCirc',
                        duration: 450,
                        begin: function (anim) {
                            anime({
                                targets: "#div3",
                                top: tempstyle[2].top,//2
                                left: tempstyle[2].left,
                                color: "rgb(255,255,255)",
                                easing: 'easeInOutCirc',
                                duration: 450,
                                complete: () => {
                                    var father = document.getElementById("pagParent").children;
                                    let p = document.getElementById("pagParent");
                                    father[2].id = "div3";
                                    father[3].id = "div2";
                                    let existingnode2 = father[3];
                                    let existingnode1 = father[2];
                                    p.insertBefore(existingnode2, existingnode1);
                                }
                            })

                        }
                    })
                }
            })
        }
    })
    // 觸發第二個div
    onClickTo1 = (tempstyle) => anime({
        targets: '#div1',
        color: 'red',
        top: tempstyle[2].top,//2
        left: tempstyle[2].left,
        easing: 'easeInOutCirc',
        duration: 450,
        begin: function (anim) {
            anime({
                targets: "#div2",
                top: tempstyle[1].top,//1
                left: tempstyle[1].left,
                easing: 'easeInOutCirc',
                duration: 450,
            })
        },
        complete: () => {
            var father = document.getElementById("pagParent").children;
            let p = document.getElementById("pagParent");
            father[1].id = "div2";
            father[2].id = "div1";
            let existingnode1 = father[2];
            let existingnode0 = father[1];
            p.insertBefore(existingnode1, existingnode0);
            anime({
                targets: "#div2",
                top: tempstyle[3].top,//3
                left: tempstyle[3].left,
                color: "rgb(228,94,35)",
                easing: 'easeInOutCirc',
                duration: 450,

                begin: function (anim) {
                    anime({
                        targets: "#div3",
                        top: tempstyle[2].top,//2
                        left: tempstyle[2].left,
                        color: "rgb(255,255,255)",
                        easing: 'easeInOutCirc',
                        duration: 450,
                    })
                },
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[2].id = "div3";
                    father[3].id = "div2";
                    let existingnode2 = father[3];
                    let existingnode1 = father[2];
                    p.insertBefore(existingnode2, existingnode1);
                }
            })
        }
    })
    // 觸發第三個div
    onClickTo2 = (tempstyle) => anime({
        targets: "#div2",
        top: tempstyle[3].top,//3
        left: tempstyle[3].left,
        color: "rgb(228,94,35)",
        easing: 'easeInOutCirc',
        duration: 450,
        begin: function (anim) {
            anime({
                targets: "#div3",
                top: tempstyle[2].top,//2
                left: tempstyle[2].left,
                color: "rgb(255,255,255)",
                easing: 'easeInOutCirc',
                duration: 450,
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[2].id = "div3";
                    father[3].id = "div2";
                    let existingnode1 = father[3];
                    let existingnode0 = father[2];
                    p.insertBefore(existingnode1, existingnode0);
                }
            })

        },
    })
    // 觸發第五個div
    onClickTo4 = (tempstyle) => anime({
        targets: "#div4",
        top: tempstyle[3].top,//3
        left: tempstyle[3].left,
        color: "rgb(228,94,35)",
        easing: 'easeInOutCirc',
        duration: 450,
        begin: function (anim) {
            anime({
                targets: "#div3",
                top: tempstyle[4].top,//4
                left: tempstyle[4].left,
                color: "rgb(255,255,255)",
                easing: 'easeInOutCirc',
                duration: 450,
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[4].id = "div3";
                    father[3].id = "div4";
                    let existingnode1 = father[4];
                    let existingnode0 = father[3];
                    p.insertBefore(existingnode1, existingnode0);
                }
            })

        },
    })
    // 觸發第六個div
    onClickTo5 = (tempstyle) => anime({
        targets: "#div5",
        top: tempstyle[4].top,//4
        left: tempstyle[4].left,
        easing: 'easeInOutCirc',
        duration: 450,
        begin: function (anim) {
            anime({
                targets: "#div4",
                top: tempstyle[5].top,//5
                left: tempstyle[5].left,
                easing: 'easeInOutCirc',
                duration: 450,
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[5].id = "div4";
                    father[4].id = "div5";
                    let existingnode1 = father[5];
                    let existingnode0 = father[4];
                    p.insertBefore(existingnode1, existingnode0);
                    anime({
                        targets: "#div4",
                        top: tempstyle[3].top,//3
                        left: tempstyle[3].left,
                        color: "rgb(228,94,35)",
                        easing: "easeInOutCirc",
                        duration: 450,
                        begin: function (anim) {
                            anime({
                                targets: "#div3",
                                top: tempstyle[4].top,//4
                                left: tempstyle[4].left,
                                color: "rgb(255,255,255)",
                                easing: "easeInOutCirc",
                                duration: 450,
                                complete: () => {
                                    var father = document.getElementById("pagParent").children;
                                    let p = document.getElementById("pagParent");
                                    father[4].id = "div3";
                                    father[3].id = "div4";
                                    let existingnode1 = father[4];
                                    let existingnode0 = father[3];
                                    p.insertBefore(existingnode1, existingnode0);
                                }
                            })

                        }
                    })
                }
            })
        }
    })
    // 觸發第七個div
    onClickTo6 = (tempstyle) => anime({
        targets: "#div6",
        top: tempstyle[5].top,//5
        left: tempstyle[5].left,
        easing: 'easeInOutCirc',
        duration: 450,
        begin: function (anim) {
            anime({
                targets: "#div5",
                top: tempstyle[6].top,//6
                left: tempstyle[6].left,
                easing: 'easeInOutCirc',
                duration: 450,
                complete: () => {
                    var father = document.getElementById("pagParent").children;
                    let p = document.getElementById("pagParent");
                    father[6].id = "div5";
                    father[5].id = "div6";
                    let existingnode1 = father[6];
                    let existingnode0 = father[5];
                    p.insertBefore(existingnode1, existingnode0);
                    anime({
                        targets: "#div5",
                        top: tempstyle[4].top,//4
                        left: tempstyle[4].left,
                        easing: 'easeInOutCirc',
                        duration: 450,
                        begin: function (anim) {
                            anime({
                                targets: "#div4",
                                top: tempstyle[5].top,//5
                                left: tempstyle[5].left,
                                easing: 'easeInOutCirc',
                                duration: 450,
                                complete: () => {
                                    var father = document.getElementById("pagParent").children;
                                    let p = document.getElementById("pagParent");
                                    father[5].id = "div4";
                                    father[4].id = "div5";
                                    let existingnode1 = father[5];
                                    let existingnode0 = father[4];
                                    p.insertBefore(existingnode1, existingnode0);
                                    anime({
                                        targets: "#div4",
                                        top: tempstyle[3].top,//3
                                        left: tempstyle[3].left,
                                        color: "rgb(228,94,35)",
                                        easing: "easeInOutCirc",
                                        duration: 450,
                                        begin: function (anim) {
                                            anime({
                                                targets: "#div3",
                                                top: tempstyle[4].top,//4
                                                left: tempstyle[4].left,
                                                color: "rgb(255,255,255)",
                                                easing: "easeInOutCirc",
                                                duration: 450,
                                                complete: () => {
                                                    var father = document.getElementById("pagParent").children;
                                                    let p = document.getElementById("pagParent");
                                                    father[4].id = "div3";
                                                    father[3].id = "div4";
                                                    let existingnode1 = father[4];
                                                    let existingnode0 = father[3];
                                                    p.insertBefore(existingnode1, existingnode0);
                                                }
                                            })

                                        }
                                    });
                                }
                            })
                        }
                    })
                }
            })
        }
    })

當新的數據來了,我們得處理新的數據,和調用上面六個動畫函數的主函數,於是有了下面的主函數

ch = (i) => {
        switch (i) {
            case 0: this.onClickTo0(this.props.leftStyleData)
                break;
            case 1: this.onClickTo1(this.props.leftStyleData)
                break;
            case 2: this.onClickTo2(this.props.leftStyleData)
                break;
            case 4: this.onClickTo4(this.props.leftStyleData)
                break;
            case 5: this.onClickTo5(this.props.leftStyleData)
                break;
            case 6: this.onClickTo6(this.props.leftStyleData)
                break;
        }
    }

當動畫執行過後,div的在網頁中的位置以及dom節點在html文檔中的順序已經調整一致後,需要將div中的數據同時替換成最新的順序,爲此需要將後臺傳來的數據臨時保存,並且進行數據的深拷貝

 todoIndexSmallThree = (index, data) => {
        let arr = [].concat(JSON.parse(JSON.stringify(data)));
        let temp = arr.splice(0, index + 1);
        let temp1 = arr.splice(0, 6 - index);
        let arrLast = temp.concat(temp1);
        [arrLast[3], arrLast[index]] = [arrLast[index], arrLast[3]];
        return arrLast;
    }
    todoIndexMiddle = (index, data) => {
        let arr = [].concat(JSON.parse(JSON.stringify(data)));
        let temp = arr.splice(index - 3, 4);
        let temp1 = arr.splice(index + 1 - 4, 3);
        let arrLast = temp.concat(temp1);
        return arrLast;
    }
    todoIndexSmallLast = (index, data) => {
        let arr = [].concat(JSON.parse(JSON.stringify(data)));
        let temp = arr.splice(index, data.length - index);
        let temp1 = arr.splice(arr.length - (7 - temp.length), arr.length - 1);
        let arrLast = temp.concat(temp1);
        [arrLast[0], arrLast[3]] = [arrLast[3], arrLast[0]];
        return arrLast;
    }
    todoData = (index, arr) => {
        var newData = [];
        switch (index) {
            case 0: {
                // 深拷貝一個數組
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[1];
                let tag1 = newArr[2];
                let tag2 = newArr[3];
                let tag3 = newArr[0];
                let tag4 = newArr[4];
                let tag5 = newArr[5];
                let tag6 = newArr[6];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
            case 1: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[0];
                let tag1 = newArr[2];
                let tag2 = newArr[3];
                let tag3 = newArr[1];
                let tag4 = newArr[4];
                let tag5 = newArr[5];
                let tag6 = newArr[6];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
            case 2: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[0];
                let tag1 = newArr[1];
                let tag2 = newArr[3];
                let tag3 = newArr[2];
                let tag4 = newArr[4];
                let tag5 = newArr[5];
                let tag6 = newArr[6];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
            case 3: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                newData = newArr;
            } break
            case 4: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[0];
                let tag1 = newArr[1];
                let tag2 = newArr[2];
                let tag3 = newArr[4];
                let tag4 = newArr[3];
                let tag5 = newArr[5];
                let tag6 = newArr[6];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
            case 5: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[0];
                let tag1 = newArr[1];
                let tag2 = newArr[2];
                let tag3 = newArr[5];
                let tag4 = newArr[3];
                let tag5 = newArr[4];
                let tag6 = newArr[6];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
            case 6: {
                var newArr = [].concat(JSON.parse(JSON.stringify(arr)));
                let tag0 = newArr[0];
                let tag1 = newArr[1];
                let tag2 = newArr[2];
                let tag3 = newArr[6];
                let tag4 = newArr[3];
                let tag5 = newArr[4];
                let tag6 = newArr[5];
                newData = [tag0, tag1, tag2, tag3, tag4, tag5, tag6];
            } break;
        }
        return newData;
    }

當後臺傳來的數據不在現有的數據集中,也就是不在左側的div中,我們不單要更換數據,還要從html結構中刪除這7個div,因爲只有生成新的div順序關係,才能確定下一波數據來臨之後執行什麼樣的動畫。這裏由於採用了iconfont,還需要利用js的生成iconfont圖標,這是一個麻煩的問題。

  for (let i = childs.length - 1; i >= 0; i--) {
                    if (n === 3) {
                        parent.removeChild(childs[i]);
                        let div = document.createElement("div");
                        let icon = document.createElement("i");
                        let ptitle = document.createElement("p");
                        parent.appendChild(div);
                        div.appendChild(icon);
                        div.appendChild(ptitle);
                        div.style.color = "rgb(228,94,35)";
                        icon.className = `${styles.iconfont}`;
                        icon.style.fontSize = this.props.IconfontStyle.size;
                        icon.innerHTML = "&#xe65d;";
                        icon.style.paddingTop = this.props.IconfontStyle.paddingTop;
                        icon.style.paddingLeft = "6%";
                        ptitle.innerHTML = defaultData[n].logicEntityGroupName;
                        div.id = `div${n}`;
                    } else {
                        parent.removeChild(childs[i]);
                        let div = document.createElement("div");
                        let icon = document.createElement("i");
                        let ptitle = document.createElement("p");
                        parent.appendChild(div);
                        div.appendChild(icon);
                        div.appendChild(ptitle);
                        div.style.color = "rgb(255,255,255)";
                        icon.className = `${styles.iconfont}`;
                        icon.style.fontSize = this.props.IconfontStyle.size;
                        icon.innerHTML = "&#xe65d;";
                        icon.style.paddingTop = this.props.IconfontStyle.paddingTop;
                        icon.style.paddingLeft = "6%";
                        ptitle.innerHTML = defaultData[n].logicEntityGroupName;
                        div.id = `div${n}`;
                    }
                    n++;
                }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章