如何优雅的实现动画效果

1.实现动画的方式

  • javaScript:setTimeout、setInterval
  • css3: transition、animation
  • html: canvas
  • requestAnimationFrame Api
    前三种我们都很熟悉,重点说说requestAnimationFrame

2.requestAnimationFrame

2.1 定义

告诉浏览器,你需要执行一个动画,并要求浏览器在下次重绘之前调用指定的更新函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在下次重绘前执行

  //html代码全文通用,所以只在此贴出一次
<body>
  <h1>requestAnimationFrame API</h1>
  <button id='begin' class="begin">开始</button>
  <button id='end' class="end">停止</button>
</body>

//js
(() => {
  function test() {
    console.log('🚀🚀hello ~ requestAnimationFrame');
  }
  requestAnimationFrame(test)
})()

上述代码只执行了一下,为什么会这样呢?看看mdn怎么说。
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
所以我们可以这么写:

  (() => {
  let n = 0
  function test() {
    n++
    console.log(`🚀🚀hello ~ requestAnimationFrame ${n}`);
    requestAnimationFrame(test)
  }
  requestAnimationFrame(test)
})()

2.2 执行频率

回调函数执行频率通常60次/秒,与浏览器屏幕刷新次数相匹配

2.3 回调参数

回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。

(() => {
  function test(timestamp) {
    console.log(`🚀🚀hello ~ requestAnimationFrame ${timestamp}`);
    requestAnimationFrame(test)
  }
  requestAnimationFrame(test)
})()

timestamp释义:在同一个帧中的 多个回调函数 ,它们每一个都会接受到一个 相同的时间戳 ,即使在计算上一个回调函数的工作负载期间已经 消耗了一些时间 。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

性能问题

3.1 浏览器自身措施:

为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的iframe 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

3.2开发人员限制

3.2.1 requestAnimationFrame会返回一个非0整数,通过cancelAnimationFrame 可停止动画

(() => {
  const beginBtn = document.querySelector("#begin")

  const endBtn = document.querySelector("#end")

  let myRef;

  beginBtn.addEventListener("click", () => {
    myRef = requestAnimationFrame(test)
  })

  endBtn.addEventListener("click", () => {
    cancelAnimationFrame(myRef)
  })

  function test() {
    myRef = requestAnimationFrame(test)
    console.log('🚀🚀~ myRef:', myRef);
  }
})()

3.2.2 在代码中做一些操作

(() => {
  function test(timestamp) {
    console.log(`🚀🚀hello ~ requestAnimationFrame ${timestamp}`);
    if (timestamp < 500) {
      requestAnimationFrame(test)
    }
  }
  requestAnimationFrame(test)
})()

4.动画例子

<style>
  #box {
    width: 0px;
    height: 50px;
    background-color: blue;
  }
</style>
<body>
  <h1>requestAnimationFrame API</h1>
  <button id='begin' class="begin">开始</button>
  <button id='end' class="end">停止</button>
  <div id='box'></div>
</body>
(() => {
  const beginBtn = document.querySelector("#begin")
  const endBtn = document.querySelector("#end")
  const box = document.querySelector("#box")
  let myRef;

  beginBtn.addEventListener("click", () => {
    myRef = requestAnimationFrame(test)
  })

  endBtn.addEventListener("click", () => {
    cancelAnimationFrame(myRef)
  })

  function test() {
    box.style.width = `${myRef}%`
    myRef = requestAnimationFrame(test)
  }
})()

5.对比

5.1 setTimeout && setInterval

setTimeout 和 setInterval 的问题是,它们不够精确。它们的内在运行机制决定了 时间间隔参数 实际上只是指定了把动画代码添加到 浏览器UI线程队列 中以等待执行的时间。如果队列前面已经加入了其它任务,那动画代码就要等前面的 任务完成后 再执行,并且如果时间间隔过短(小于16.7ms)会造成丢帧,所以就会导致动画可能不会按照预设的去执行,降低用户体验。
requestAnimationFrame 采用 浏览器时间间隔 ,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,消耗性能;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个 统一 的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

5.2 CSS3动画

CSS3 的transition 和 animation 搭配使用可以说是非常强大了,但是也有的触手伸不到的地方,比如说
scrollTop,另外 CSS3 动画支持的贝塞尔曲线也是有限的。
那么,CSS3 做不到的就可以用到 requestAnimationFrame 来解决了。

tips 利用该方法可以代替setTimeout

例如 每间一秒执行一次函数

    (() => {
    let startTime = Date.now();

    function handleTicker() {
      foo(Date.now() - startTime);
      startTime = Date.now();
      requestAnimationFrame(handleTicker);
    }

    requestAnimationFrame(handleTicker);

    let t = 0
    function foo(timeInterval) {
      t += timeInterval
      console.log('🚀🚀~ t:', t);
      if (t > 1000) {
        console.log('🚀🚀~ 搞事情');
        t = 0
      }
    }
  })()

参考:

posted @ 2023-02-15 11:01  菜鸟小何  阅读(53)  评论(0编辑  收藏  举报