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

CSS Animation Worklet #24

Open
anjia opened this issue Oct 13, 2018 · 4 comments
Open

CSS Animation Worklet #24

anjia opened this issue Oct 13, 2018 · 4 comments
Labels
CSS cascading style sheets css-houdini

Comments

@anjia
Copy link
Owner

anjia commented Oct 13, 2018

简介

CSS Animation Worklet API 是 CSS Houdini 的一部分。关于 Houdini 的介绍,可查看 #23

这个 API 扩展了 Web 动画堆栈。具体来说:

  • 它扩展了 timelines,让网页开发人员能编排动画效果
  • 它可以控制 stateful(有状态) 的动画效果了。这是驱动动画的一种新方式
    • eg. scroll-driven 滚动驱动的动画
    • eg. 还有其它形式的,还在赶来的路上... 敬请期待

举几个例子,大家来感受下什么是 有状态 的动画。

比如 Chrome 的顶部地址栏(点此链接查看动画效果),它的显隐不仅取决于滚动位置,还取决于滚动方向。i.e. 当上拉页面向下滚动时,它就隐藏了;当下拉页面向上滚动时,它又回来了,且不管是否有没有滚动到页面的顶部。

比如视差滚动,目前在 Web 上实现不太容易,详见 Performant Parallaxing

比如自定义滚动条样式,让猫作为滚动条,要么需要我们自己监听滚动事件,然后还要确保动画流畅又不耗性能;要么实现起来不容易

有了 Animation Worklet API,我们就可以非常直接且简单地控制此类动画效果了。

https://developers.google.com/web/updates/2018/10/animation-worklet

@anjia anjia added CSS cascading style sheets css-houdini labels Oct 13, 2018
@anjia
Copy link
Owner Author

anjia commented Oct 13, 2018

以下代码均需在 Chrome Canary 中打开,并在 chrome://flags 里开启 Experimental Web Platform features。记得开启后,重启下浏览器

首先,我们看下 Animation Worklet 是怎么扩展 timelines 的。来看个例子

控制动画的时间线

最终的效果是:

源码见 src/css-animation-worklet/demo1.helloworld

在 index.html 里

<div id="demo1"></div>

<script>
  if('animationWorklet' in CSS) {

    async function init() {
      await CSS.animationWorklet.addModule('my_aw.js'); // 加载 Animation Worklet

      new WorkletAnimation(
        'hellworld',  // aw的名字,在my_aw.js里定义的
        new KeyframeEffect(
          document.querySelector('#demo1'), 
          [
            {
              transform: 'translateX(0)'
            },
            {
              transform: 'translateX(500px)'
            }
          ],
          {
            delay: 2000,
            duration: 5000, 
            iterations: Number.POSITIVE_INFINITY
          }
        ),
        document.timeline
      ).play(); 
    }

    init();
  }else{
    console.warn('您的浏览器暂不支持 Animation Worklet');
  }  
</script>

每个文档都有个document.timeline,从文档初始化时开始,它从0计时,存储文档存在的毫秒数。文档的所有动画,都和这个 timeline 有关。

当调用animation.play()时,动画就用 timeline 的currentTime值作为它的开始时间startTime 。在上面的代码里,我们设置了delay: 2000,这意味着当 timeline 到startTime+2000ms时该动画就开始执行。之后,引擎会让指定的元素在规定的时间duration: 5000内,按照代码里给定的关键帧序列,从第一个关键帧执行到最后一个。这样,当 timeline 到startTime+2000ms+5000ms时,动画就恰好执行到了最后一个关键帧。然后再跳到第一个关键帧,开始动画的下一个迭代,如此往复,因为我们设置了iterations: Number.POSITIVE_INFINITY。在此期间,timeline 控制着我们的整个动画过程。

在 my_aw.js 里

// 定义了一个名字是 hellworld 的 Animation Worklet
registerAnimator('hellworld', class {
  animate(currentTime, effect) {
      effect.localTime = currentTime;
  }
});

animate()函数,浏览器在渲染每一帧时,就会调用它。

  • 参数currentTime,是动画 timeline 的当前时间
  • 参数effect,是当前正在处理的效果

在函数体内,我们只是简单的将currentTime赋给了effect.localTime,这样动画就动起来了。

@anjia
Copy link
Owner Author

anjia commented Oct 14, 2018

接下来,我们继续改造代码,来看看在 Animation Worklet 里还能做什么。

自定义时间线

在上面的示例中,我们只是通过effect.localTime = currentTime让动画线性地动起来了。当然,我们也可以自定义任意时间线。比如:

animate(currentTime, effect) { 
  let minIn = -1, maxIn = 1, 
      minOut = 0, maxOut = 3000; // 映射到时间范围[minOut, maxOut]
  let v = Math.sin(currentTime * 2 * Math.PI / maxOut);  // Math.sin()

  effect.localTime = (v - minIn)/(maxIn - minIn) * (maxOut - minOut) + minOut;
}

运行的效果是:

传参数

Animation Worklet 也支持传参数。具体代码如下:

在 index.html 里

new WorkletAnimation(
  'sin',
  new KeyframeEffect(...),
  document.timeline,
  { maxOut: 5000 }  // 1.传参
).play();

在 my_aw.js 里

registerAnimator('sin', class {
  // 2.接收参数 options
  constructor(options = {}) {
      this.maxOut = options.maxOut || 3000;
  }
    
  animate(currentTime, effect) { 
      let minIn = -1, maxIn = 1, 
          minOut = 0,
          maxOut = this.maxOut;  // this.maxOut
      let v = Math.sin(currentTime * 2 * Math.PI / maxOut);

      effect.localTime = (v - minIn)/(maxIn - minIn) * (maxOut - minOut) + minOut;
  }
});

完整代码见 src/css-animation-worklet/demo2

@anjia
Copy link
Owner Author

anjia commented Oct 14, 2018

有状态的动画

之前我们有提到,Animation Worklet(简称 AW)想要解决的关键问题之一就是有状态的动画。也就是说它要能保持住状态。

但是,Worklet 的核心功能之一是它们可以迁移到不同的线程,甚至可以被销毁以节省资源,这样就会破坏 AW 的状态。

为了防止状态丢失,AW 提供了一个钩子,在 Worklet 被销毁之前调用,在那儿可以返回状态对象。等下次再重新创建时,AW 的构造函数会接收到这个状态对象(初始创建时,值是 undefined)。

具体代码如下:

registerAnimator('randomspin', class {
  // 2. 接收状态参数,第二个参数 state
  constructor(options = {}, state = {}) {
    this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
  }
  animate(currentTime, effect) {
    effect.localTime = 2000 + this.direction * (currentTime % 2000); // this.direction
  }

  // 1. 钩子函数 destroy(),返回想要保存的状态信息
  destroy() {
    return {
      direction: this.direction
    };
  }
});

最终的效果及代码见 animation-worklet-state。每次刷新页面时,动画的方向都是重新生成的。为了让再次刷新页面之后动画的运动方向不变,我们保存了this.direction状态。

注意,生命周期的钩子函数destory()已经被 getter 方法取代了,但这种变化还没有反映在规范里或者 Chrome 的实现上。所以,咱们这里只重点介绍思路。

@anjia
Copy link
Owner Author

anjia commented Oct 14, 2018

上面介绍的动画都是 time-driven 的,下面我们来看个 scroll-driven 的动画实例。

滚动驱动的动画

它的使用非常简单:

  • 使用时,把之前的document.timeline换成new ScrollTimeline()
  • 注册时,直接effect.localTime = currentTime

具体代码如下:

在 index.html 里

new WorkletAnimation(
  'scrollDriven',
  new KeyframeEffect(...),
  // document.timeline,  // 不要它了,换成 ScrollTimeline
  new ScrollTimeline({
    scrollSource: document.querySelector('#scroll-area'), 
    orientation: "vertical", // "horizontal" or "vertical".
    timeRange: 3000
  })
).play(); 

ScrollTimeline用的不是 time,而是 scrollSource 滚动位置,来设置 AW 里的currentTime。当滚动到顶部(或左侧)时,currentTime是0,当滚动到底部(或右侧)时,currentTimetimeRange

在 my_aw.js 里

registerAnimator('scrollDriven', class {
  animate(currentTime = 0, effect) {
    effect.localTime = currentTime;
  }
});

最终的效果是:当滚动文本框时,红色的色块也会跟着动。如下:

完整代码见 src/css-animation-worklet/demo3.scroll

@anjia anjia mentioned this issue Oct 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CSS cascading style sheets css-houdini
Projects
None yet
Development

No branches or pull requests

1 participant