借助Web Animations API实现JS keyframes动画 -- 文字无限循环滚动

1,122 阅读2分钟

记录工作中小思考

背景

对该API的挖掘,源于一个需求

  1. 用css3实现一个文字无限循环滚动的组件,不要用setInterval
  2. 当鼠标点击上去的时候,动画停止,用户可以通过滚动条滚动查看
  3. 鼠标离开时,动画继续

文字无限滚动,网上还是有很多参考的,这个方案不用担心。纠结的是,鼠标hover上去暂停、恢复动画是没问题的,但是动画的滚动是没有scrollTop的,那怎么知道滚动了多少,怎么可能只用css就实现上述需求呢??而且鼠标hover离开还要让滚动条消失出现的,这需求没法做吧。。。

格局小了,又没说只能用一个div,可以用两个div来实现,一个div是无动画效果有滚动条的div,另一个是有动画效果无滚动条的div,鼠标hover上去的时候,切换到无动画效果的div上,鼠标离开时再切回到有动画效果的div上

那么实现这个需求的关键点是,1需要知道动画的滚动的距离,2需要将滚动条滚动的距离告诉动画,让动画接着滚🤣

起初我对动画效果的了解主要是css3的animation,于是我就查怎么知道animation的滚动距离,然后发现了CSS Object Model (CSSOM),可以用js操作css的API,允许动态的读取和修改css样式,之前没这么用过,所以还真不知道。但是我在使用的时候,可能用法也不对把,能获取到页面其他的样式,但是总是获取不到动画样式,于是我就发现这个方法可能不是太适合,就继续查阅别的方法。

找了好久,发现了Web Animation API - 在 JavaScript 中释放 CSS keyframes 的力量,这太关键了,一下子解决了技术难点。 关键:effect.getComputedTiming().progress、currentTime

好了,方案都有了,开始实现

效果

rowup.gif

实现

功能点

  1. 文字无限循环滚动 image.png

  2. 滚动动画效果和鼠标滚动随鼠标是否hover进行切换,并且两中方式滚动的元素位置要衔接好 主要用到:

effect.getComputedTiming().progress:动画的滚动进度,乘上文字区域高度,就知道滚动了多少距离了 currentTime:通过设置这个属性可以控制动画当前滚动的位置

在线效果及代码(代码很好理解,直接看代码吧)


附vue组件代码,上下滚动效果,其他的滚动方向自行调整translate3d y轴参数

<template>
  <div
    :id='scrollWrapperId'
    :class='wrapperClassName'
  >
    <div
      class="scroll-hidden"
      :style="{height:height,width:width}"
    >
      <div class='scroll-content'>
        <slot></slot>
        <slot></slot>
      </div>
    </div>
    <div
      class="scroll-auto"
      :style="{width:width}"
    >
      <div>
        <slot></slot>
        <slot></slot>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'Scroll',
  props: {
    wrapperClassName: String,
    scrollWrapperId: {
      type: String,
      default: 'scroll-wrapper'
    },
    height: String, // 带单位
    width: {
      type: String,
      default: '100%'
    },
    options: { // keyframes参数
      type: Object,
      default: () => ({
        duration: 3000
      })
    }
  },
  mounted() {
    let scrollTop;
    const scrollHiddenDom = document.querySelector(`#${this.scrollWrapperId}  > .scroll-hidden`);
    const scrollAutoDom = document.querySelector(`#${this.scrollWrapperId}  > .scroll-auto`);
    const animationContentDom = document.querySelector(`#${this.scrollWrapperId}  > .scroll-hidden > .scroll-content`);
    const offsetHeight = animationContentDom.offsetHeight / 2;

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/263e8ed773cf4a488b9fa3930341bd3e~tplv-k3u1fbpfcp-watermark.image)
    const keyFrames = [
      {
        transform: 'translate3d(0, 0, 0)',
        offset: 0 // 0%
      }, {
        transform: 'translate3d(0, -50%, 0)',
        offset: 1 // 100%
      }
    ];
    const keyFramesOptions = {
      duration: this.options.duration / 1,
      delay: 0,
      iterations: Infinity,
      easing: 'linear',
      fill: 'forwards', // 定义当动画不再播放时,动画应如何将样式应用于其目标 当动画未播放时,目标元素将保留最后关键帧中定义的计算样式
      direction: 'normal'// 定义动画是否应该正常播放
    };

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/146f6fc2ec6a4741a189a0f817a18f01~tplv-k3u1fbpfcp-watermark.image)
    if (!animationContentDom.animate) { // 如果浏览器不支持滚动 就直接展示滑动区域
      scrollHiddenDom.style.visibility = 'hidden';
      scrollHiddenDom.style.height = 0;
      scrollAutoDom.style.visibility = 'visible';
      scrollAutoDom.style.height = this.height;
      return;
    }
    const myanimation = animationContentDom.animate(keyFrames, Object.assign({}, keyFramesOptions, this.options));

    scrollHiddenDom.addEventListener('mouseenter', () => {
      myanimation.pause();
      const animationProcess = myanimation.effect.getComputedTiming().progress; // animation滚动进度  范围0-1
      scrollTop = offsetHeight * animationProcess;

      scrollHiddenDom.style.visibility = 'hidden';
      scrollHiddenDom.style.height = 0;
      scrollAutoDom.style.visibility = 'visible';
      scrollAutoDom.style.height = this.height;

      scrollAutoDom.scrollTop = scrollTop;
    });
    scrollAutoDom.addEventListener('mouseleave', () => {
      scrollHiddenDom.style.visibility = 'visible';
      scrollHiddenDom.style.height = this.height;
      scrollAutoDom.style.visibility = 'hidden';
      scrollAutoDom.style.height = 0;
      // 通过滚动的距离,计算对应动画的时间值  jump to x second from start of animation
      myanimation.currentTime = scrollAutoDom.scrollTop / (offsetHeight / this.options.duration / 1);
      myanimation.play();
    });
  }
};
</script>
<style lang='scss' scoped>
.scroll-hidden {
  visibility: visible;
  overflow: hidden;
}
.scroll-auto {
  visibility: hidden;
  height: 0;
  overflow: auto;
}
</style>

参考: luchun.github.io/web-animati… www.xiabingbao.com/css3/2017/0… www.cnblogs.com/coco1s/p/13…