vue+帧动画 实现 获奖奖品列表滚动循环展示

实现效果

vue+帧动画 实现 获奖奖品列表滚动循环展示

 初级方法:

实现原理:

  1. 由于列表的总数是变化的,所以不能用css把动画写死,通过定时器移动列表,实现动画效果
  2. 计算总高度,建一个变量存储移动距离,两者之前比较,当移动距离>=总高度 就把移动距离重置为0
  3. 在css中给列表盒子绝对定位,通过移动top值实现动画

注意事项:由于需要循环滚动,可以把获取到的数组在复制一份放到列表里,做到无缝衔接

html:

   <div class="inner_box">
        <div class="prize_container">
          <ul
            ref="prizeRef"
            class="prize_ul"
            v-if="info.joinList && info.joinList.length"
          >
            <li
              class="prize_item"
              v-for="(item, index) in info.joinList"
              :key="index"
            >
              <div class="left">{{ item.mobile | mobileFilter }}</div>
              <div class="right">{{ item.name }}</div>
            </li>
          </ul>
        </div>
     </div>

js:

// 获取活动信息
    getData() {
      this.fetch
        .get("TjModule/GetActivityInfo", {
          activityId: this.id
        })
        .then(res => {
          if (res.data && res.data.success) {
            this.$setTitle(res.data.result.activityName);
            this.info = res.data.result;
            let arr = [];
            if (res.data.result.joinList.length) {
              arr = res.data.result.joinList;
              arr.map(i => {
                arr.push(i);
              });
            }
            this.info.joinList = arr;
            console.log(this.info.joinList);
            this.$nextTick(() => {
              this.changeAnimation(this.info.joinList.length);
            });
          }
        })
        .catch(error => {
          if (error.response && error.response.status === 500) {
            this.$toast(error.response.data.error.message);
          }
        });
    },
    // 编写动画
    changeAnimation(d) {
      console.log(9090);
      let total = 25.5 * d/2
      let every = 0
      const dm = this.$refs.prizeRef;
      this.timers = setInterval(()=>{
        every += 5
        if(every < total){
          dm.style.top = - every + 'px'
        }else{
          dm.style.top = 0
          every = 0
        }
      },120)
    },

// 关闭定时器
   beforeDestroy() {
       clearInterval(this.timers)
   },

css:

     .prize_container {
        overflow: hidden;
        position: absolute;
        height: 90%;
         100%;
        padding: 30px 0;
      }

      .prize_ul {
         100%;
        color: #fff;
        position: absolute;
        padding: 0px 80px;
        // animation: opacityShow linear 5s infinite;
        .prize_item {
           100%;
          display: flex;
          justify-content: space-between;
          margin-bottom: 12px;
        }
      }

在changeAnimation函数中, let total = 25.5 * d/2  这个25.5是每行的高度,通过 every(移动距离) 与 total(总高度) 比较,实现循环

缺点:

  1. 滚动的时候总是感觉一卡一卡的效果,不够流畅( 原因: 浏览器每秒刷新的次数 和 每次移动距离的时间频率 不一致,导致视觉上产生卡顿效果 )
  2. 通过top值进行移动,比较损耗性能,每次改变都会影响布局
  3. 获取总高度total,不要手动计算,应该通过 clientHeight 动态获取比较好
高级方法:

使用一个属性: 帧动画  window.requestAnimationFrame() 
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
这个方法挂载在window上,很方便我们的使用
 
实现原理:
  1.  通过控制 transform 的 translateY值进行移动动画
  2. 动态获取列表高度,padding,margin去掉,否则会在计算高度的时候,有误差,循环衔接点会有卡顿
  3. requestAnimationFrame与animate函数相互调用,执行帧动画requestAnimationFrame时,调用移动动画的函数,实现同频率改变    requestAnimationFrame(this.animate) 

    animate() {
        requestAnimationFrame(this.animate);
     this.changeAnimation();
    },

html:

      <div class="prize_container">
          <div class="prize_list"  :style="{ transform: 'translateY( -'+ ulLeft + 'px)' }" >
            <ul
              ref="prizeUl"
              class="prize_ul"
              v-if="info.joinList && info.joinList.length" >
              <li
                class="prize_item"
                v-for="(item, index) in info.joinList"
                :key="index"
              >
                <div class="left">{{ item.mobile | mobileFilter }}</div>
                <div class="right">{{ item.name }}</div>
              </li>
            </ul>
            <ul
              class="prize_ul"
              v-if="info.joinList && info.joinList.length" >
              <li
                class="prize_item"
                v-for="(item, index) in info.joinList"
                :key="index"
              >
                <div class="left">{{ item.mobile | mobileFilter }}</div>
                <div class="right">{{ item.name }}</div>
              </li>
            </ul>
          </div>
      </div>

这里使用复制两个ul元素,不在js中操作复制列表

js:

data(){
    prizeUlHeight:0,  //滚动ref高度
    $prizeUl:null,  //滚动的ref
    ulLeft:0, //移动参数 
}
 beforeDestroy() {
    cancelAnimationFrame(this.times); //清理原生的监听
  },

methods:{
   // 获取活动信息
   getData() {
      this.fetch
        .get("TjModule/GetActivityInfo", {
          activityId: this.id
        })
        .then(res => {
          if (res.data && res.data.success) {
            this.$setTitle(res.data.result.activityName);
            this.info = res.data.result;
            this.$nextTick(() => {
              this.$prizeUl = this.$refs.prizeUl;
              this.prizeUlHeight = this.$prizeUl.clientHeight;
              this.times =  requestAnimationFrame(this.animate);
            });
          }
        })
        .catch(error => {
          if (error.response && error.response.status === 500) {
            this.$toast(error.response.data.error.message);
          }
        });
    },
    animate() {
      requestAnimationFrame(this.animate);
      this.changeAnimation();
    },
    // 编写动画
    changeAnimation(d) {
      if( this.ulLeft == this.prizeUlHeight ){
        this.ulLeft = 0;
      }else{
        this.ulLeft += 1;
      }
    },

}

css:

.prize_container {
     overflow: hidden;
     position: absolute;
     height: 90%;
      100%;
}

.prize_ul {
      100%;
     color: #fff;
     // position: absolute;
     padding: 0px 80px;
     .prize_item {
         100%;
        display: flex;
        line-height: 58px;
        justify-content: space-between;
    }
}

这样就完美解决了循环滚动动画,

使用帧动画还有一个优点:  

每次切换页面的时候,帧动画会暂停执行,直到切换回该页面,才会继续执行动画,这样就比定时器更加保护性能

其实还有一个方案:

用css + js

  1. css中写动画,把动画属性分开写, animation-duration 动态计算:根据每行花费几秒 * 几行 这样动态赋值
  2. css中通过控制 transform 的 translateY值进行移动50%即可
// 写动画
@keyframes opacityShow {
        from {
          transform: translateY(0);          
        }
        to {
          transform: translateY(-50%);          
        }
}
.prize_ul {
         100%;
        color: #fff;
        padding: 0px 80px;
        animation-name: opacityShow;
        animation-iteration-count: infinite;
        .prize_item {
           100%;
          display: flex;
          line-height: 58px;
          justify-content: space-between;
        }
 }
 

 使用