Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

太卡了,巧妙地优化了跑马灯 #45

Open
Sunny-lucking opened this issue Jul 11, 2022 · 0 comments
Open

太卡了,巧妙地优化了跑马灯 #45

Sunny-lucking opened this issue Jul 11, 2022 · 0 comments

Comments

@Sunny-lucking
Copy link
Owner

前言

上周优化了个跑马灯,原因是跑马灯的长度太长了,每个item的节点比较多,所以即使限制最多只有50个item,也还是很长很长,有多长可以看看下面

怎么优化呢?看看之前的跑马灯

优化前的写法

之前的写法很简单,其实就是让很长很长的class="animate"的div在lottery-person-wrapper中滚动。用的是css 中的animation属性。

用animation虽然好,但是不能控制跑马灯的长度,即我不想让50个item一起滚动,最好是让只需要出现在屏幕中的item滚动就好了。于是就将滚动改成了item为绝对定位,然后利用transform来改变位置,然后利用transition来实现动画的过渡。

优化后的写法

可以看到没有那么多的item节点了,这是怎么办到的呢?

  1. 首先获取lottery-person-wrapper的宽度
this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
  1. 然后再让一个item出现在跑马灯中。
mounted() {
  this.$nextTick(() => {
     this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
     this.emitItem();
   });
}
  1. 看看emit是怎么写的

首先需要知道

  • swiperUserList是从接口获取到的列表
  • swiperUserListShow是在template中遍历的列表

我们先拿出swiperUserList中的第一个item,然后再把item放入swiperUserList的尾部,让swiperUserList始终保持50个item。

然后,再把这个item深拷贝放入到swiperUserListShow中,为什么要深拷贝是因为,不希望swiperUserListShow的item与swiperUserList中的item出现引用的关系,否则会十分混乱。

给每一个item添加了一个id是为了作为遍历时独一无二的key

接下来则是要获取该item的宽度clientWidth,然后计算出该item的尾部出现的时间endShowTime,以及该item完全走完消失的时间disappearTime

在该item尾部出现的时候,就让下一个item push到swiperUserListShow中,使其出现在跑马灯中,在该item完全跑完消失的时候就让这个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);
      });
    },

基本上就已经实现了。

为什么说是基本?

因为有两个坑。

看看坑

第一个是我们用了setTimeout,在我们将页面切到后台的时候,setTimeout里的代码是挂起的,不会执行,但是页面上的动画还是会继续执行的

elm.style.transition = `transform ${disappearTime}s linear`;
elm.style.transform = 'translate(-100%,-50%)';

所以,为了解决这个bug,需要监听是否切出切入后台,切到后台则清除所有setTimeout和清空swiperUserListShow列表,切回页面,再重新执行emitItem。

mounted() {
  this.$nextTick(() => {
     this.animationWrapperWidth = this.$refs.animateWrapper.clientWidth;
     this.emitItem();
   });
   
   // 处理退出前台,跑马灯还在跑的问题,隐藏就是直接清空展示列表
    document.addEventListener('visibilitychange', () => {
      const isShow = document.visibilityState === 'visible'
      this.handleSwiperListShow(isShow);
    });
}
  methods: {

    // 处理跑马灯展示列表和清除计时器
    handleSwiperListShow(isShow) {
      if (isShow) {
        this.emitItem();
      } else {
        this.swiperUserListShow.forEach((item) => {
          clearTimeout(item.endShowTimer);
          clearTimeout(item.disappearTimer);
        });
        this.swiperUserListShow = [];
      }
    },
  }

第二个坑是我们使用了clientWidth来获取item的宽度,当我们页面中有tab的时候,并且跑马灯在某个tab下,然后当前v-show是激活的是其他tab,则会导致跑马灯被隐藏,则获取不到item的宽度,这时的clientWidth的值为0.导致计算出来的endShowTime的值为0,则会导致疯狂执行settimeout里面的内容

为了解决这个bug则需要在父组件中传入isShow来判断跑马灯这个页面是否被隐藏

  props: {
    isShow: {
      type: Boolean,
      default: false
    },
  }

然后监听isShow

 watch: {

    // 处理tab选项卡隐藏抽奖模块,获取不到item clientWith的问题,隐藏就是直接清空展示列表
    isShow(newVal, oldVal) {
      this.handleSwiperListShow(newVal)
    }
  },

至此,优化过程就到此完美结束了。

其实还有个比较简单的优化方法,但是不适用于我这个场景,但是也分享一下。

就是依然使用css的animation动画属性,然后使用animationEnd的监听事件,

其他优化方案

当监听到结束的时候,利用v-if把当前跑马灯销毁,然后就往swiperUserListShow中push两个item,再生成展示跑马灯,又实现animation动画,这样是一个实现起来十分方便的方案,但是由于同一时刻只有我们push的item数,而且需要跑完才继续展示下两个,会留下一片空白,就有的不连贯的感觉,所以不使用这种方案。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant