coconilu/Blog

通过Volocity.js源码看JS如何顺滑操作动画

Opened this issue · 0 comments

顺滑的动画

在网页开发圈子里有一种误解,那就是认为CSS动画是网络中唯一可以实现高性能动画的方法。因为CSS可以借助硬件加速把动画直接交给GPU处理。

事实上,基于JavaScript的动画与基于CSS的动画一样快。之所以JS动画会卡顿,或许是因为你使用定时器(setTimeout、setInterval)去处理动画了,但这不是JS的错,而是你的使用方法错了。

基于JS的动画有如下的优点:

  1. 动画中止
  2. 动画反转
  3. 动画管理

CSS非常适合实现悬停状态的动画效果(例如:当鼠标位于链接上方时,链接变成蓝色)这类的微互动,这也是通常情况下基本的网页所包含的动画。

Velocity.js核心原理

Velocity.js是一个著名的动画库。这是个轻量级的库,但是功能却异常丰富。

GitHub地址奉上:https://github.com/julianshapiro/velocity

为了研究JS是如何优雅处理动画的,我们通过一段动画效果,观察火焰图去解析:

动画效果演示链接在这

核心JS是:

document.querySelector("#v").velocity(
  {
    opacity0.5,
    left1000,
  },
  {
    duration2000,
  }
);

下图是帧率和CPU利用率的示意图:

D1B288C4-0908-4943-8CBB-E441706712F8

在CPU那一行,可以看到,有很多执行点,我们放大其中一个:

81EAB795-0D01-4528-BCA8-86B34BF05ECE

我们可以看到Animation Frame Fired,其实就是requestAnimationFrame()回调被触发了,接着执行tick和setPropertyValue。我们一个一个看:

78928622-7267-42B0-8FB5-FE9919573AB2

点击跳转到源码:

9B8210AC-5ADE-4A6E-971C-53DF93E79CEC

跳转到rAFShim,可以看到这是对 requestAnimationFrame 的调用,tick 是它的回调:

rAFProxy = (callbackFrameRequestCallback) => {
  return setTimeout(callback, Math.max(0, FRAME_TIME - (performance.now() - lastTick)));
},

rAFShim = window.requestAnimationFrame || rAFProxy;

跳转到 setPropertyValue

function setPropertyValue(element, propertyName, propertyValue, fn) {
    var noCache = NoCacheNormalizations.has(propertyName),
        data = !noCache && Data(element);
    if (noCache || data && data.cache[propertyName] !== propertyValue) {
        // By setting it to undefined we force a true "get" later
        if (!noCache) {
            data.cache[propertyName] = propertyValue || undefined;
        }
        fn = fn || getNormalization(element, propertyName);
        if (fn) {
            fn(element, propertyValue);
        }
        if (Velocity$$1.debug >= 2) {
            console.info("Set \"" + propertyName + "\": \"" + propertyValue + "\"", element);
        }
    }
}

我们继续深究上面的fn函数,发现它是 getSetStyle返回的函数,函数最后会设置元素的style属性:

function getSetStyle(propertyName) {
    return function (element, propertyValue) {
        if (propertyValue === undefined) {
            return computePropertyValue(element, propertyName);
        }
        element.style[propertyName] = propertyValue;
    };
}

至此,我们可以理顺 Velocity.js 的核心原理:

  1. 当我们通过velocity去设置某个元素的最终样式,velocity会计算出初始值,在演示里是 opacity 和 left
  2. 然后通过注册requestAnimationFrame()回调去计算动画的中间态,并应用在元素上

Velocity.js 是通过计算值处理动画效果的中间值的。所以对几何长宽、颜色、透明度这些值非常友好。

不仅如此,Velocity.js还可以终止动画,也可以反转动画,这些都是CSS设置动画办不到的,JS可以通过在执行requestAnimationFrame回调前终止动画,并且通过记录历史中间态并反转。

搭配React使用

Velocity.js 搭配 React 使用的库叫 velocity-react。

GitHub地址奉上:https://github.com/google-fabric/velocity-react

velocity-react 暴露两个组件接口:

  1. VelocityComponent,添加动画,为子组件注册动画效果;
  2. VelocityTransitionGroup,添加过渡效果(显示和隐藏组件,切换组件),这个组件里面的所有子组件的mount、unmount都会触发过渡效果,底层依赖TransitionGroup(react-transition-group)组件。

还有一个API接口:velocityHelpers,用以注册动画效果的。

参考

《JavaScript网页动画设计》
Velocity.js
https://github.com/google-fabric/velocity-react