太長(zhǎng)了,巧妙地優(yōu)化了跑馬燈

前言
上周優(yōu)化了個(gè)跑馬燈,原因是跑馬燈的長(zhǎng)度太長(zhǎng)了,每個(gè)item的節(jié)點(diǎn)比較多,所以即使限制最多只有50個(gè)item,也還是很長(zhǎng)很長(zhǎng),有多長(zhǎng)可以看看下面

















怎么優(yōu)化呢?看看之前的跑馬燈

優(yōu)化前的寫法





之前的寫法很簡(jiǎn)單,其實(shí)就是讓很長(zhǎng)很長(zhǎng)的class="animate"的div在lottery-person-wrapper中滾動(dòng)。用的是css 中的animation屬性。

用animation雖然好,但是不能控制跑馬燈的長(zhǎng)度,即我不想讓50個(gè)item一起滾動(dòng),最好是讓只需要出現(xiàn)在屏幕中的item滾動(dòng)就好了。于是就將滾動(dòng)改成了item為絕對(duì)定位,然后利用transform來(lái)改變位置,然后利用transition來(lái)實(shí)現(xiàn)動(dòng)畫的過(guò)渡。

優(yōu)化后的寫法



可以看到?jīng)]有那么多的item節(jié)點(diǎn)了,這是怎么辦到的呢?

首先獲取lottery-person-wrapper的寬度
this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
然后再讓一個(gè)item出現(xiàn)在跑馬燈中。
mounted() {
  this.$nextTick(() => {
     this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
     this.emitItem();
   });
}
看看emit是怎么寫的
首先需要知道

swiperUserList是從接口獲取到的列表
swiperUserListShow是在template中遍歷的列表



我們先拿出swiperUserList中的第一個(gè)item,然后再把item放入swiperUserList的尾部,讓swiperUserList始終保持50個(gè)item。

然后,再把這個(gè)item深拷貝放入到swiperUserListShow中,為什么要深拷貝是因?yàn)?,不希望swiperUserListShow的item與swiperUserList中的item出現(xiàn)引用的關(guān)系,否則會(huì)十分混亂。

給每一個(gè)item添加了一個(gè)id是為了作為遍歷時(shí)獨(dú)一無(wú)二的key



接下來(lái)則是要獲取該item的寬度clientWidth,然后計(jì)算出該item的尾部出現(xiàn)的時(shí)間endShowTime,以及該item完全走完消失的時(shí)間disappearTime

在該item尾部出現(xiàn)的時(shí)候,就讓下一個(gè)item push到swiperUserListShow中,使其出現(xiàn)在跑馬燈中,在該item完全跑完消失的時(shí)候就讓這個(gè)item從swiperUserListShow中剔除。






    emitItem() {
      if (!this.isShow) {
        return;
      }
      let swiperUser = this.swiperUserList.shift();
      this.swiperUserList.push(swiperUser);
      this.swiperUserListShow.push(
        Object.assign({}, { ...swiperUser, id: this.swiperId })
      );
      this.swiperId += 1;
      this.$nextTick(() => {
        let elm = this.$refs.swiperUserList[this.swiperUserListShow.length - 1];

        let elmWidth = elm.clientWidth || 0;

        let disappearTime = (elmWidth + this.animationWrapperWidth) / 60;
        let endShowTime = elmWidth / 60;

        let moveItem =
          this.swiperUserListShow[this.swiperUserListShow.length - 1];
        elm.style.transition = `transform ${disappearTime}s linear`;
        elm.style.transform = 'translate(-100%,-50%)';
        // this.clearTimer(moveItem)
        moveItem.endShowTimer = window.setTimeout(() => {
          clearTimeout(moveItem.endShowTimer);
          moveItem.endShowTimer = null;
          this.emitItem();
        }, endShowTime * 1000);

        moveItem.disappearTimer = window.setTimeout(() => {
          clearTimeout(moveItem.disappearTimer);
          moveItem.disappearTimer = null;
          this.swiperUserListShow.shift();
        }, disappearTime * 1000);
      });
    },
基本上就已經(jīng)實(shí)現(xiàn)了。

為什么說(shuō)是基本?

因?yàn)橛袃蓚€(gè)坑。

看看坑
第一個(gè)是我們用了setTimeout,在我們將頁(yè)面切到后臺(tái)的時(shí)候,setTimeout里的代碼是掛起的,不會(huì)執(zhí)行,但是頁(yè)面上的動(dòng)畫還是會(huì)繼續(xù)執(zhí)行的

elm.style.transition = `transform ${disappearTime}s linear`;
elm.style.transform = 'translate(-100%,-50%)';
所以,為了解決這個(gè)bug,需要監(jiān)聽(tīng)是否切出切入后臺(tái),切到后臺(tái)則清除所有setTimeout和清空swiperUserListShow列表,切回頁(yè)面,再重新執(zhí)行emitItem。

mounted() {
  this.$nextTick(() => {
     this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
     this.emitItem();
   });
   
   // 處理退出前臺(tái),跑馬燈還在跑的問(wèn)題,隱藏就是直接清空展示列表
    document.addEventListener('visibilitychange', () => {
      const isShow = document.visibilityState === 'visible'
      this.handleSwiperListShow(isShow);
    });
}
  methods: {

    // 處理跑馬燈展示列表和清除計(jì)時(shí)器
    handleSwiperListShow(isShow) {
      if (isShow) {
        this.emitItem();
      } else {
        this.swiperUserListShow.forEach((item) => {
          clearTimeout(item.endShowTimer);
          clearTimeout(item.disappearTimer);
        });
        this.swiperUserListShow = [];
      }
    },
  }
第二個(gè)坑是我們使用了clientWidth來(lái)獲取item的寬度,當(dāng)我們頁(yè)面中有tab的時(shí)候,并且跑馬燈在某個(gè)tab下,然后當(dāng)前v-show是激活的是其他tab,則會(huì)導(dǎo)致跑馬燈被隱藏,則獲取不到item的寬度,這時(shí)的clientWidth的值為0.導(dǎo)致計(jì)算出來(lái)的endShowTime的值為0,則會(huì)導(dǎo)致瘋狂執(zhí)行settimeout里面的內(nèi)容

為了解決這個(gè)bug則需要在父組件中傳入isShow來(lái)判斷跑馬燈這個(gè)頁(yè)面是否被隱藏

  props: {
    isShow: {
      type: Boolean,
      default: false
    },
  }
然后監(jiān)聽(tīng)isShow

 watch: {

    // 處理tab選項(xiàng)卡隱藏抽獎(jiǎng)模塊,獲取不到item clientWith的問(wèn)題,隱藏就是直接清空展示列表
    isShow(newVal, oldVal) {
      this.handleSwiperListShow(newVal)
    }
  },
至此,優(yōu)化過(guò)程就到此完美結(jié)束了。

其實(shí)還有個(gè)比較簡(jiǎn)單的優(yōu)化方法,但是不適用于我這個(gè)場(chǎng)景,但是也分享一下。

就是依然使用css的animation動(dòng)畫屬性,然后使用animationEnd的監(jiān)聽(tīng)事件,





其他優(yōu)化方案
當(dāng)監(jiān)聽(tīng)到結(jié)束的時(shí)候,利用v-if把當(dāng)前跑馬燈銷毀,然后就往swiperUserListShow中push兩個(gè)item,再生成展示跑馬燈,又實(shí)現(xiàn)animation動(dòng)畫,這樣是一個(gè)實(shí)現(xiàn)起來(lái)十分方便的方案,但是由于同一時(shí)刻只有我們push的item數(shù),而且需要跑完才繼續(xù)展示下兩個(gè),會(huì)留下一片空白,就有的不連貫的感覺(jué),所以不使用這種方案。









作者:事業(yè)有成的張啦啦


歡迎關(guān)注微信公眾號(hào) :前端陽(yáng)光