写得我脑子都乱了, 后面决定分成章节写, 所以先看章节几个标题吧:
前端动画库的问题
声明式写动画组件
Quamolit 项目来源
Quamolit 方案的思路
Quamolit 方案的实现
Quamolit 存在的问题
声明式写动画的前景
前端动画库的问题
经常看新闻的话, 有相当多的动画库. 其实很多知识封装 API 而已
当然 API 是好的, 提供更多兼容性可靠性, 自动完成更多的抽象
但是呢, 这些 API, 比如 twojs 的 API 提供很多对象
你要创建什么, 实例化一个对象, 然后用方法调用给对象发消息,
必要的话还有销毁对象实例. 总之, 手动处理实例每个细节
其中操作还是很琐碎, 特别当项目越来越大, 问题会更清晰
我写了挺长时间单页面应用, 管理对象实例这个事情其实很明确了
比如 DOM 节点是实例吧, 各种操作都有 API, 而且 jQuery 封装得不错
但是呢, 如果一个界面有上千个 DOM 节点, 你怎么管理
很自然地, 你会需要模板引擎, 需要模板组件, 然后能嵌套起来渲染
你需要编程语言里强大的抽象, 封装, 隔离, 从而分解问题解决问题的能力
你需要界面像是函数调用一样能嵌套, 能复合, 能从很简单的结构组合出复杂的程序
大部分的动画库, 其实并不是为了解决这类问题, 这也许是框架的问题
声明式写动画组件
当然, 我的思路基本上是从 React 这边借鉴的, 声明式写动画的例子
React 当中可以写一个节点, style
中有个数据根据条件表达式计算
比如可能是 width: (if isSelected then 40 else 20);
这样的样式
然后加上 transition-duration: 300ms
, 声明渐变, 这样就有动画了
这是属性改变的动画, 另外还有组件 mount 和 unmount 时的动画
这个在 CSSTransitionGroup
插件当中通过 on-enter
on-leave
提供了
这是很简单的代码生成动画的例子, 声明式的写法
动画当然远远不止这么简单, 而且 React 的动画组件效果也不好
动画还会有时钟动画那样, 一直在改变从不停止的
只是说在交互界面当中, 进入, 改变, 退出, 三种情况加上渐变, 一般足够了
那么我们能看到一种可能性, 如果方案做得更完善, 声明式写动画是可行的
开发者只要声明好, 那么组件的进入, 改变, 退出过程, 能由歘通自动生成渐变
这对于图形界面的效果的改善能起到直接的帮助
Quamolit 项目来源
关于 Quamolit 我前面已经码过三篇文章, 篇幅也不少了
https://segmentfault.com/t/quamolit/blogs
前面几段也讲了, 最初是 React 里展示的例子, 声明式的动画
然而深入思考下去, React 做不好动画是有原因的
DOM 渲染开销不小, 不能像 WebGL 那样一直高速修改界面
DOM 即便是 SVG, 它也是为文档设计的, 绘制图形不够灵活
按照 MVC 的方法, 动画也是数据和视图两部分的, CSS 动画把两者绑死了
因而我认为靠谱的动画方案, 还是需要基于 Canvas API 实现
目标是达到 React 这样, 每个组件有独立性, 然后能够组合
记得是在 Teambition 2015 年年会前, 我开始尝试早期的代码
Quamolit 方案的思路
问题集中在几个方向上, 其实前面的博文有说明的, 我这里只是梳理一下
界面绘制的组件化
需要确定, 界面是能够像函数那样, 作用域内部和外部形成一定的隔离
那么, 比如这个组件是先移动到然后旋转, 最后绘制的, 组件本身内部不需要知道
就像是一个函数不需要关心是谁调用的, 都要能够正确返回内容
而这一点, 通过 Canvas API 已有的 ctx.save()
和 ctx.restore()
可以实现
另外 translate
, alpha
之类的方法调用, 也可以封装成为元素
动画状态和动画界面分离
动画进行到什么位置, 这是在编程语言运行时当中有数字应对的
在 CSS 里, JavaScript 没法探知动画到了哪里, 而 Canvas 天然解决了
就需要有地方存储组件动画状态的数据, 我直接放在了组件上
组件数据更新或者被卸载, 框架会管理其数据
声明式的框架当中, 值的改变是瞬间的, 组件的创建删除也是瞬间的
为了能有动画, 就需要让这些过程变成在时间上延续而且渐变的行为
所以要在框架中设计机制, 把时间生成出来. 这就看具体实现了
单纯说思路还是比较简单的, 麻烦在于中间的编程技巧还有 bug 上边
我写的 Quamolit 代码, 基本上都是靠模拟 React 的组件结构来做的
至少 React 的思路比较成熟, 而且组件层级也是现成的
Quamolit 方案的实现
其实整体思路在一年前定下来就没多少改进了, 但实现方面存在问题
一是我当时用的大量随意的状态修改, 代码本身很不稳健
二是列表动画引出了父子组件关联动画的问题, 我想不清楚
问题的关键围绕在实现的思路, 实现不完整, 导致各种链条断裂
过来一年我熟悉了 cljs, 而且有了比较充裕的时间, 侥幸找到了一个解法
一是从 Respo 的 DOM Diff 方案照搬的声明式组件转过程式指令的手法
二是认识到了速度作为历史操作的压缩的状态, 有着控制的作用
这些很大获益于 FP 对于程序的状态, 依赖和抽象的知识和理解
最终让我幸运地完成了目前 Quamolit 的十多个 Demo 当中的许多功能
借鉴 DOM Diff
由于组件 DSL 得到的是 Virtual DOM 一样的数据, 没法做动画
我需要查找出两次界面渲染的改变, 并且用来生成动画
有一天我突然想起来, 在计算 DOM Diff 时, 得到的就是声明式的指令啊
当然, 也会更复杂点, 因为组件和元素在 Diff 中需要区分, 前者生成动画
比如我发现组件 state 某个值改了, 可以给组件设置一个速度, 后面再处理为动画
引入速度概念
前一篇文章解释了, 速度的信息当中包含了过去所有操作对未来的总体影响
比如在一个动画过程当中, 混入了其他多个操作的动画, 不用速度很难处理
而且我用不同维度的速度来表示不同的属性改变
Quamolit 的 cljs 实现当中, 我把动画状态存储在组件上, 方便查找和清理
细节需要查看代码, 语言描述毕竟有限...
Quamolit 存在的问题
我知道 cljs 会是很多人的门槛, 几乎很少听到国内有人也用过 cljs
但用 js 的话我恐怕走不到这么远, 中间的 bug 和奇怪的功能太多了
而且我认为 ES6 乱搞下去, 复杂度要超过 cljs 了, 试试 cljs 吧
前面有文章介绍而且制作了视频用来证明写基础的 cljs 已经很容易了
其他存在于 Quamolit 本身的问题, 说实在蛮严重的, Demo 看不出来
但如果考虑在生产环境使用, 这些问题每一个都是致命的
ctx.addHitRegon()
API
这个说很多次了, 目前仍然是试验功能, 一年多了, 估计是标准制定问题
这是用来确定 Canvas 上点击位置属于哪个 Path 的
没有这个 API 的话, 我需要手工计算鼠标点在哪个组件上, 非常困难
性能问题
Quamolit 目前我 Demo 的几个应用, 都是很烧 CPU 的
原因主要是每次计算都会处理整个 virtual DOM, 随着节点增加而消耗
因为没法从外部确定组件深处是否有动画, 只能整个查找一遍
我其实尝试过设计缓存机制, 但除掉 bug 不说, 本身的性能还是不理想
也许是 cljs 调用上存在问题, 然后当然是嵌套太深的影响
Canvas 交互弱
毕竟不是 DOM 那么多事件已经由浏览器处理好了, Canvas 仅仅是画布
因而提供的交互相当有限, 用来做 App, 目前网页中很多交互都失去了
然而如果要做, 整个工作量必定不会小就是了, 这可是从头做
速度抽象不够
这一点相对不那么致命, 只是写代码的时候回明显觉得有问题
就是说, 写起来代码会很啰嗦, 重复逻辑挺多, 肯定是还要再进行抽象的
我现在还没有想很清楚, 走一步看一步吧
声明式写动画的前景
Quamolit 进展到现在, 我微博上发的 demo 已经挺多了
我其实希望有人来一起探索, 只是文档和门槛都不容易
现在能看到的主要是项目 README 和一个 cljs 写的示例项目
https://github.com/Quamolit/quamolit
http://www.tudou.com/programs/view/uaKilL-Iu5o/
我认为记忆复杂度已经很低了, 但理解的难点不知道会在哪
需要等到有反馈才会慢慢形成概念, 我也只能这样了
虽然前面一节说了无法进生产, 但是今天的 2048 游戏 Demo 完成度却很高
至少从这些 Demo 我认为, 在技术上我们距离声明式写动画已经很近了
至少在 Chrome 里稍加配置, 已经能把 2048 游戏渲染到 DOM 优化的效果了
到这一步我已经很明确这条路肯定是通的, 只是路有多宽的问题
而且前面 Bezier 曲线的 Demo, 让我自己也有点意外,
感觉就是曾经的一个愿望现在实现了, 用很简单的代码画出了漂亮的动态图形
甚至不止这一个, 好多个从前觉得很难写出来的动画, 我都一天内搞定了, 开心
最后, 如果你看过某些视频里描述未来的 VR 的 UI, 你会发现,
那些界面的动画比现在我们能做的漂亮得多, 状态的管理上也复杂得多
比如 Magic Leap 最新宣传片里管理邮件的那些个界面动画:
https://www.youtube.com/watch?v=GmdXJy_IdNw
再者 Quamolit 做的还只是 2D 的, 路还很远呢