WebAnimation 与 FLIP 技术

Web Animations API

类似于 CSS in JS 的 animation 实现.

  • 基本使用

    • 定义关键帧

      使用对象数组的模式定义关键帧

      const aliceTumbling = [
          { transform: 'rotate(0) translate3D(-50%, -50%, 0)', color: '#000' },
          { color: '#431236', offset: 0.3},
          { transform: 'rotate(360deg) translate3D(-50%, -50%, 0)', color: '#000' }
      ],

      使用 offset 指定关键帧出现在动画位置的百分比, 首尾对象默认 offset = 0/1

      若不指定 offset 则直接选取中点作为 offset, 例如

      [
       {/*...*/}, // <- 第一个元素, offset = 0
       {offset: 0.4}, // <- 手动指定, offset = 0.4
       {/*...*/}, // <- 未指定, 均分为 0.4 + (1-0.4)/3*1 = 0.6
       {/*...*/}, // <- 未指定, 均分为 0.4 + (1-0.4)/3*2 = 0.8
       {/*...*/} // <- 最后一个元素, offset = 1
      ]
    • 定义动画执行模式

      const aliceTiming = {
        duration: 3000,
        iterations: Infinity
      }
    • 执行动画

      const anim = elem.animate(aliceTumbling, aliceTiming)
  • Hooks

    • 下面的 anim 就是 elem.animate() 的返回值
    • anim.play() / anim.pause(): 执行 / 暂停动画. 注意, 动画在 animate() 的时候回自动执行, 如需手动控制需要立刻 pause 一下
    • anim.playbackRate: 动画执行速率(可以为负数), 可写属性
    • anim.currentTime: 动画执行时间, 可写属性
    • anim.effect.timing.duration: 动画持续时间, 类似属性见 MDN
    • anim.finish(): 动画结束
    • anim.cancel(): 终止动画
    • Animation.reverse(): 反向播放动画
    • document.getAnimations(): 获取全部 Web Animation 注册动画
    • anim.onfinish(callback): 动画结束回调
    • anim.oncancel(callback): 动画取消回调

FLIP 技术

当我们需要对 DOM 的位置做调整但是又不知道目标位置的具体时可以用 FLIP 实现带有过渡动画的位置调整.

例如, 有 a b c d 元素, 我们希望将元素变为 d c b a. 可以直接通过 DOM API 调整位置, 但是无法实现动画. FLIP 的做法是先将元素的起始位置记下来, 再调整到目标位置, 再通过 CSS 将元素调到原为止, 最后通过动画完成过渡

  • First:在任何事情发生之前,记录将要转换的元素的当前(即第一)位置和尺寸。您可以使用element.getBoundingClientRect()它,如下所示。
  • Last:执行使过渡瞬间发生的代码,并记录元素的最终(即last)位置和尺寸。
  • Invert:由于元素位于最后一个位置,我们想通过transform修改其位置和尺寸来创建它位于第一个位置的错觉。这需要一点数学运算,但并不难。
  • Play:元素反转(并假装在第一个位置),我们可以通过将其设置为transform来将其移回到最后一个位置none

vue 2 文档中提到的效果

动画实现的比较

动画的实现方法:

  • 纯 CSS (animation / transition) with GPU
  • 纯 JS (requestAnimationFrame & style)
  • WebAnimation API

其中

  • JS 实现的动画会被同步代码阻塞. 但是更加灵活

    requestAnimationFrame(/*...*/)
    while(true); // <- request 卡死
  • 不使用 GPU 渲染的 CSS 动画会被同步代码阻塞, 采用 GPU 的不阻塞. 只要动画涉及的属性不引起 reflow 动画的采样就会交给 GPU 处理.

    #alice_css {
      animation: cssRound infinite 3s linear;
    }
    @keyframes cssRound {
      0% {
        left: 0;  /* <- left 会引发重绘, 此时动画的渲染是由 CPU 完成的, 如果执行 while(true); 动画就会卡死*/
      }
    
      100% {
        left: 300px;
      }
    }
    #alice_css {
      animation: cssRound infinite 3s linear;
    }
    @keyframes cssRound {
      0% {
        transform: rotateZ(0deg); /* <- rotateZ 不会引发重绘, 此时动画的渲染是由 GPU 完成的, 如果执行 while(true); 动画不会卡死*/
      }
    
      100% {
        transform: rotateZ(360deg);
      }
    }

    因此, 能使用 CSS 动画的就不要用 JS 动画

  • WebAnimation API: 通过 JS 的方式定义动画, 最终会将动画效果通过 CSS 动画完成, 其对同步 JS 代码阻塞的表现与 CSS 动画一致

    这个 API 既保留了 JS 的灵活性(控制动画执行, 动画执行观测, 与JS交互), 同时使用类似 CSS 的方式执行动画, 减少了同步代码对动画的影响

    const aliceJs = document.getElementById('alice_js');
      const aliceRoundJs = [
        { transform: 'rotateZ(0deg)' }, // <- 同步代码阻塞也卡死
        { transform: 'rotateZ(360deg)' },
      ];
      const aliceTimeJs = {
        duration: 3000,
        iterations: Infinity,
      };
      aliceJs.animate(aliceRoundJs, aliceTimeJs);
    const aliceJs = document.getElementById('alice_js');
    const aliceRoundJs = [
      { left: '0' }, // <- 同步代码阻塞也不卡死
      { left: '300px' },
    ];
    const aliceTimeJs = {
      duration: 3000,
      iterations: Infinity,
    };
    aliceJs.animate(aliceRoundJs, aliceTimeJs);