TAT.dnt Omi 拥抱 60FPS 的 Web 动画
In 未分类 on 2018年11月07日 by view: 6,290
0

写在前面

Omi 框架 正式发布了 → omi-transform

Made css3 transform super easy. Made 60 FPS easy.

作为 Omi 组件化开发特效运动解决方案,让你轻松在 Omi 项目里快速简便支持 CSS3 Transform 设置。css3transform 是经受过海量项目洗礼的,作为移动 Web 特效解决方案,在微信、手 Q 兴趣部落、日迹、QQ 群、QQ 附近等项目中广泛使用,以激进的修改 DOM 属性为代价,带来极为便利的可编程性。

你可以通过 css3transform 官方首页快速了解它。

上面官网的例子都是原生 js 的,css3transform 也拥有 react 版本,你也可以在 react 中以声明式的方式使用 css3transform:

这都不是重点,重点是 omi-transform。

3 分钟掌握 omi-transform

通过 npm 安装

使用

  • 把需要运动的 DOM 使用 <css3-transform></css3-transform> 包裹
  • 在需要使用 css3transform 的 DOM 上标记 ref 用来直接操作 DOM
  • 在组件函数里便可以使用 this.refs.animDiv 来读取或者设置 css transform 属性
  • this.refs.xxx 支持 "translateX", "translateY", "translateZ", "scaleX", "scaleY", "scaleZ", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "originX", "originY", "originZ", "perspective" 这些属性设置和读取
  • perspective 表示透视投影的距离

组件里的某个 DOM 在运动过程中,可能会由于其他逻辑,进行 update。有可能是用户交互,有可能是数据返回的回调。所以,update 前后,DOM 的状态的保留显得尤其重要,不然的话就会有闪烁、跳跃的效果或者其他显示逻辑错误。

可以看到上面的代码在 DOM 运动过程中时不进行 Diff ?组件不进行 update ?万一组件 update,所有运动的状态都会丢失?Omi 怎么解决这个问题?上面的代码已经给出了答案:

使用 this.data.rotateZ 来同步运动 DOM 的状态防止意外的刷新 (update)

→ 演示

支持的属性

Property Describe
translateX translateX
translateY translateY
translateZ translateZ
scaleX scaleX
scaleY scaleY
scaleZ scaleZ
rotateX rotateX
rotateY rotateY
rotateZ rotateZ
skewX skewX
skewY skewY
originX the basic x point of rotation
originY the basic y point of rotation
originZ the basic z point of rotation
perspective Perspective projection distance

你既可以 get 也可以 set。

性能对比

因为 react 版本会有 diff 过程,然后 apply diff to dom 的过程,state 改变不会整个 innerHTML 全部替换,所以对浏览器渲染来说还是很便宜,但是在 js 里 diff 的过程的耗时还是需要去 profiles 一把,如果耗时严重,不在 webworker 里跑还是会卡住 UI 线程导致卡顿,动画卡顿丢帧、交互延缓等。所以要看一看 CPU 的耗时还是很有必要的。

下面数据是对比 omi-transform 和 react-transform,两种方式使用 chrome profiles 了一把。

先看总耗时对比

react-transform:

omi-transform:

  • react 在 8739 秒内 CPU 耗时花费了近似 1686ms
  • Omi 方式在 9254ms 秒内 CPU 耗时花费近似 700ms

在不进行 profiles 就能想象到 react 是一定会更慢一些,因为 state 的改变要走把 react 生命周期走一遍,但是可以看到 react 的耗时还是在可以接受的范围,没有慢到难以接受。

而 Omi 的方式则和传统的原生 js 的耗时一模一样。因为运动过程不进行 DOM Diff,直接操作 DOM!!

Omi 自身对比

主要对比上面两个代码块的执行效率,打开谷歌浏览器的 Performance 运行 10 秒左右,打开 Summary 对比:

Slow Fast
Omi Omi

可以看到 omi 的两种方式都拥有很高性能,10 秒钟内拥有大量的空闲时间,但是 fast 确实更加 fast,scripting 的耗时更短。但是优势不明显是为什么?因为 DOM 结构简单,如果 DOM 结构越复杂, fast 直接操作 DOM 的方式会把 slow 的方式甩开一大截!下面进行验证一下:

先发 render 的 DOM 结构修改成复杂的:

Omi

打开谷歌浏览器的 Performance 运行 10 秒左右,打开 Summary 对比:

Slow Fast
Omi Omi

可以看到 Scripting Time 已经拉开了差距!

对比前后两次的数据:

DOM 结构 Slow Fast
简单 Omi Omi
复杂 Omi Omi

可以看到 Fast 的前后两次没有太大差别,Slow 前后两次差距巨大。那么 Fast 内核 css3transform 原理是什么?

css3transform

安装

API

通过上面一行代码的调用,就可以设置或者读取 domElement 的"translateX", "translateY", "translateZ", "scaleX", "scaleY", "scaleZ", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "originX", "originY", "originZ"!

大道至简。

使用姿势

传统的 CSS3 编程的问题

以前,我们一般使用 animate.css、zepto/jQuery 的 animate 方法或者 tween.js+css3 进行交互特效编程。总结下来有三个缺点:

  • 不直观
  • 不直接
  • 不方便

不直观

看下面这张图:

顺序影响结果,不直观。那么为什么会是这个结果?可以通过 new WebKitCSSMatrix(transform_str) 对比最终的 matrix。

这也直接说明了矩阵不符合交换律。A*B != B*A

不直接

zepto 姿势:

translate3d: '0,10px,0'非常不方便,无法 step 递进递减控制。更别提配合一些运动或者时间的库来编程了。可能你会反驳'ease-out'不就可以实现缓动吗?但是如果我需要让 x 和 y 以及 z 分别对应不同的缓动函数,这种基于字符串编程的形式就费劲了~~
这里还需要注意的是,zepto 里的顺序也会影响结果。因为其最后也是拼成 string 赋给 dom 元素。

tween.js 姿势

使用字符串的方式,看着就心累。更别提写的过程要遭受多少折磨。

animate.css 姿势:

animate.css 封装了一大堆关键帧动画,开发者只需要关心添加或者移除相关的动画的 class 便可以。这一定程度上给交互特效带来了极大的遍历,但是要有硬伤:

  • 可编程性不够高
  • 适用于简单场景
  • 只有 end 回调,没有 change 回调

不方便

transform 的旋转点基准点默认是在中心,但是有些是时候,不系统在中心,我们传统的做法是使用 transform-origin 来设置基准点。

注意,是另一个属性 transform-origin,而不是 transform。但是如果需要运动 transform-origin 呢?这种设计是不是就废了?有没有需要运动 origin 的场景?这个在游戏设计中是经常会使用的到,这个以后另外单独开篇再说,事实就是,有场景是需要运动 origin 来达到某种效果。

小结

基于上面种种不便,所以有了 css3transform!

  • css3transform 专注于 CSS3 transform 读取和设置的一个超轻量级 js 库,大大提高了 CSS3 transform 的可编程性
  • css3transform 高度抽象,不与任何时间、运动框架捆绑,所以可以和任意时间、和运动框架轻松搭配使用
  • css3transform 使用 matrix3d 为最终输出给 dom 对象,硬件加速的同时,不失去可编程性
  • css3transform 拥有超级易用的 API,一分钟轻松上手,二分钟嵌入真实项目实战
  • css3transform 扩展了 transform 本身的能力,让 transform origin 更加方便

实战

你可以配合 createjs 的 tweenjs ,轻松制作出上面的摇摆特效:

上面的代码很精简。这里稍微解释下:

  • 元素的初始 skewX 是-20,为了和 scale 的步调一致
  • 元素的 originY 是 100,为的以企鹅的 bottom center 为基准点

可以看到,由于 css3transform 高度抽象,可以和 tweenjs 轻松搭配使用,没有任何压力。

原理

css3transform 不仅仅可以 mix CSS3 transform 到 DOM 元素,还能 mix 到任意的对象字面量,也可以把 css3transform 当作工具,他提供一些基础的数学能力。

这里需要特别注意,以前的姿势可以继续使用,这里另外三种使用姿势。

语法 1

如你所见,其他方式都不用变。只是第一个参数不仅仅可以传 DOM 元素,也可以传任意对象字面量等。

不卖关子,先看使用姿势

看到了没有,你不仅可以传 DOM 元素进去,也可以传对象字面量。你可以把 obj.transform 打印出来,上面是选择了 90 度,所以它生成出来的 matrix 是:

你同样也可以关闭透视投影,如:

生成出来的 matrix 是:

那么运动的姿势呢?这里配合 tween.js 的示例如下:

那么如果用传统的姿势是?

这里由于 TWEEN.Tween 会去遍历所以的属性并且设置初始值,如 tween 里面的代码:

所以不能直接把 new TWEEN.Tween(element)。
因为在 start 之前,程序其实已经可以完全收集到所有需要 to 的属性,去运动便可以。我们可以自己封装一个 tween 去支持这种简便的方式。如:

这里为了简便使用 setInterval 去进行 loop,当然可以换成其他方式。现在便可以使用如下方式:

当然这有点跑题了。这里只是对比直接使用 DOM 挂载和使用第三方对象挂载的区别。第三方挂载有点隔山打牛的感觉。
当然..,还没有完,不仅仅可以上面那个样子。那还可以把 css3transform 完全当作一个计算工具来用。

语法 2

姿势

打印出来你将得到下面的值:

你想用这个值来干什么就干什么吧。看 css3transform 源码可以得到 Transform.getMatrix3D 一共支持的属性:

语法 3

不仅仅是 3D matrix, css3transform 也提供了 2D 的工具函数支持。

姿势

打印出来你将得到下面的值:

  • a 水平缩放
  • b 水平拉伸
  • c 垂直拉伸
  • d 垂直缩放
  • tx 水平位移
  • ty 垂直位移

那么得到这个 Matrix2D 有什么用?

  • 缩放:scale(sx, sy) 等同于 matrix(sx, 0, 0, sy, 0, 0);
  • 平移:translate(tx, ty) 等同于 matrix(1, 0, 0, 1, tx, ty);
  • 旋转:rotate(deg) 等同于 matrix(cos(deg), sin(deg), -sin(deg), cos(deg), 0, 0);
  • 拉伸:skew(degx, degy) 等同于 matrix(1, tan(degy), tan(degx), 1, 0, 0);

看 css3transform 源码可以得到 Transform.getMatrix2D 一共支持的属性:

特别注意事项

Transform.getMatrix2D 和 Transform.getMatrix3D 都是支持 origin 特性,请和 transform-origin 说拜拜
Transform.getMatrix2D 和 Transform.getMatrix3D 没有使用传统的 Math.tan 去实现 shew,取而代之的是 half of rotation

如 2d 的 skew:

以前腾讯 IEG 的同学问过为什么使用 half of rotation,而不使用 Math.tan?
原因很简单,Math.tan 扭曲力度特别大,而且会有无穷大的值导致扭曲横跨整个屏幕。

而 half of rotation 则不会。

getMatrix2D 有用吗?

用于 Dom Transformation 时候,可以用于兼容不支持 CSS3 3D Transforms 的浏览器

如,我们可以很轻松的把一些 transformation 属性转换成 CSS3 属性赋给 DOM:

用于 Canvas 和 SVG Transformation

什么?还能用于 Canvas 和 SVG? 是的,举个例子,在 Canvas 画一个旋转 30 度、缩小成 0.5 倍,并且平移(200,200)的图片:

上面是我们传统的姿势。使用 Transform.getMatrix2D 之后,变成这个样子:

可以看到,这里让开发者不用自己去拼凑 matrix。SVG 的粒子就不再举例,和用于 DOM 的例子差不多,相信大家能够很快搞定。

Star & Fork

→ omi-transform

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2018/11/13436/

发表评论