呃~ 貌似好久没写文章了, 感觉有点奇怪废话不多说, 但还是加点前戏吧
不想听废话, 直入主题>>
前戏
为何要写这篇文章
接下来因工作调整, 应该就很少接触 H5 开发了借此机会总结对动画的一些个人思考
本文贴合实战, 会结合笔者为数不多的开发案例进行讲解最后, 也会提供相应文件让你实践
为何别人实现的动效恰到好处?
同一份设计稿给到不同开发者, 结果可能千差万别而结果主要由两部分体现内在与外在内在指的是代码质量性能优化, 外在则指的是视觉还原度和动效(交互)
其中对于更直观的外在来说, 视觉还原度高是前提, 真正体现差距的是动效因为设计师一般只给到静态的视觉稿, 而无动画演示, 更不用说提供动效搞 (如 AE) 了
在这种情况下, 页面的动效更多是由前端开发者自由发挥因此对动效有钻研的同学优势尽显他们积累了一定理论知识和经验我也曾问过这些同学, 他们大多回答是: 多试多调因此, 在设计师无动效稿提供的情况下, 都需要花时间慢慢调整, 以达到各方 (自己设计师产品和需求方等) 满意若没有设计动效等相关知识的学习与积累, 恐怕是一只没头苍蝇
关于动画的理论方面, 笔者并没有积累, 但推荐一些不错的资料 (或许需要梯子) 同时也希望得到读者们的有效补充:
动画的十二原则 12 Principles of Animation (Official Full Series)
动画的第一步是观察 Exploring Animation And Interaction Techniques With webGL (A Case Study), 译文>>
其实可以把锅扔给设计师
大多数前端开发者在设计和动效方面并没有太多积累, 因而难以做出令人拍手称赞的效果其实, 这是设计师 (或动效设计师) 所擅长的领域, 可从下表查看两者的对比:
* | 设计师 | 前端开发 | 备注 |
---|---|---|---|
是否擅长动画 | 大部分 | 少部分 | |
如何生产动画 | GUI 工具,如 AE | 编写代码 | |
效益 | 高 | 低 | 体现在以下几个方面: 1. 专业度 < br ow="0" oh="0">2. 实现效率:可视化 > 编写代码 < br ow="0" oh="0">3. 沟通成本 < br ow="0" oh="0">4. 各方满意度 < br ow="0" oh="0">5. … |
从上表可得, 将动效设计交给设计师能显著提高效益从实际工作流程上说:
设计师与前端开发的排期由线性变为部分重叠: 设计师交付静态视觉稿后, 前端开发就能进行视觉还原, 设计师此时即可进入动效设计
设计师将动效设计导出为视频, 提前取得各方满意度, 避免开发期间的反复沟通修改
假设达成以上共识后, 剩下的问题就是: 如何还原动效稿?
补间动画——Apple | 逐帧动画——洗衣机 |
设计师输出的动效演示
注: 全文动效稿均基于 Adobe After Effects(简称 AE)设计
AE 到 Web 实现
其实, 与制造业一样, 实现方式就两种:
机械: 通过工具直接导出
手工: 手动取参数, 通过掌握的 Web 技术实现
两者的优缺点比较:
* | 机械 | 手工 |
---|---|---|
效率 | 高 | 低 |
精度 | 高 | 视情况而定 |
定制化 | 低 | 高 |
情怀 | 无 | 因人而异 |
机械实现
机械代表着未来高效业界出现了很多优秀的工具, 使得在浏览器渲染复杂动效成为了可能, 且极大地提高了效率
代表工具有:
Bodymovin 是 AE 的一个插件, 用于将 AE 导出为 Web 动画(htmlSVG 或 Canvas), 仅支持 AE 部分特性
lottie-web 是 Airbnb 团队的一个用于在 WebAndroidiOS 和 React Native 渲染 AE 动画的库
可是世界上本来就没有十全十美的东西机械化生产可能未必满足所有要求, 生产环境上的要求就更加苛刻了主要体现在: 机械化生产导致介入难度高若出现以下问题就难以解决:
兼容性
在动画过程中插入自定义逻辑
工具自身的不完善
文件体积要求
无论如何, 机械化是未来, 期待它以完美的姿态到来
手工实现
手工代表着自定义可控性无论世界如何工具化, 总有一些人保持着对手工的热情
手工意味着从无到有的过程需要我们参与这个过程的每一步这也就使得我们拥有了很强的自定义能力这恰恰是机械化目前所不具备的特性这也是本文重点阐述的内容
基于 AE 手工实现 Web 动画的主要工作有两个:
在动效稿上拿到元素的参数信息, 如 x/y/zrotation 等
通过适当的 Web 技术进行实现, 如 CSS3/Canvas/SVG 等
如何手工取参
Web 动画一般分为 逐帧动画 和 补间动画
显然, 对于取参操作来说, 逐帧动画比补间动画的工作量要大得多, 但两者操作一致所以下面以 补间动画 Apple 为例:
打开 apple.aep 文件, AE 界面如下:
AE 界面
点击信息模块预览面板的播放按钮或拖动时间轴模块的 标记 3 即可预览动画
根据 CSS3 animation 属性, 我们需要获取以下信息:
动画持续时间 animation-duration
关键帧之间的缓动函数
animation-timing-function
动画延时时间 animation-delay
为了方便阐述, 我们选取整个 Apple 动画中一个小圆圈 (共 60 个) 为代表, 其余元素同理
另外, 由于该动画是一次性的, 无需设置 / 获取动画的重复次数(
animation-iteration-count
)运动方向(
- animation-direction
- )
现在我们把目光投向图层运动模块的 标记 1:
标记 1FPS
由上图可得, FPS 为 12, 即 1 秒 12 帧, 1 帧 0.0833 秒
由上面 Apple 动画 可看出, 每个圆的延时时间 (animation-delay) 缓动函数(
animation-timing-function
)和持续时间 (animation-duration) 均不相同换句话说, 每个圈都是一个独立的补间动画, 所有元素组合起来才是一个完整的补间动画
双击标记 2, 进入编组以查看每个圆的信息
子元素圆
在查看器或图层运动模块任意选中一个圆, 展开其 变换 属性并单击 位置 (标记 1), 即可显示右侧的元素运动路径(标记 2) 同时这也反映了属性的变化速率(即缓动函数(
animation-timing-function
), 这方面会在后面详解
位置 前面的时钟图标为蓝色时, 代表有过渡动画
某个圆的时间轴
结合上面知识, 可从上图得出以下信息点:
该元素共有 4 个关键帧
只有 Y 轴上发生位移运动(绿线),X 轴上则是静止状态(红线)
延时时间为 1 帧
中间停留时间 (第 23 关键帧之间) 为 1 帧
过渡时间为 42 帧(3 12 + 7 - 1)* 注意要减去延时时间(1), 因为 02:03 包含了它下同
因此, 我们基于 CSS3 animation 实现该元素的补间动画:
- <div class="circle-29"></div>
- /* 默认定位在第 2(或 3)帧以让元素默认显示屏幕内, 便于开发调试 */
- .circle - 29 {
- width: 60px;
- height: 60px;
- background - color: rgba(0, 224, 93, .7);
- position: absolute;
- left: 473px;
- top: 348px;
- border - radius: 50 % ;
- animation - name: circle29;
- animation - duration: 3.5s;
- /* 42 * (1 / 12) */
- animation - delay: 0.0833s;
- /* 1 * (1 / 12) */
- animation - fill - mode: both;
- animation - timing -
- function: ease - in-out;
- }@keyframes circle29 {
- 0 % {
- transform: translate3d(0, 1175px, 0);
- }
- 61.90 % {
- /* (2 * 12 + 3 - 1) / 42, 注意要减去延时时间(1), 因为 02:03 包含了它下同 */
- transform: translate3d(0, 0, 0);
- }
- 64.29 % {
- transform: translate3d(0, 0, 0);
- }
- 100 % {
- transform: translate3d(0, -1225px, 0);
- }
- }
这样就完成了某个圆的补间动画了虽然繁琐, 但是省去反复试验的时间, 基本做到一次开发即可使各方满意的效果
其余元素按照以上步骤执行即可完成整个动画
假设没有动画演示和动效稿, 仅凭借着个人感觉, 编码完成一个由 60 多个元素组成的动画, 简直难于上青天(对于笔者来说)
也许你对
animation-timing-function
存在误解
细心的读者可能发现: 如果第 12 帧和第 34 帧的缓动函数不相同时, 该怎么办?
首先部分人可能对
animation-timing-function
存在误解: 它是作用于整个 @keyframes 规则的
其实缓动函数是作用于 @keyframes 规则内的关键帧若未为关键帧指定
animation-timing-function
, 则会从其元素取得
animation-timing-function
更严格地说, 缓动函数是应用在属性上, 从定义该属性的关键帧到下一个指定同样属性的关键帧若后续无指定该属性的关键帧则到动画结束因此, 在 100% 或 to 关键帧上指定
animating-timing-function
是无用的
举个例子:
- .box {
- width: 100px;
- height: 100px;
- background - color: #6190e8;
- animation: move 2s ease both;
- }@keyframes move {
- 0 % {
- animation - timing -
- function: linear;
- transform: translateX(0);
- opacity: 1;
- }
- 50 % {
- opacity: .5;
- }
- 100 % {
- transform: translateX(100px);
- opacity: 1 animation - timing -
- function: ease - in-out;
- /* 无用多余 */
- }
- }
在 0% 关键帧中指定的
animation-timing-function: linear
是对 transform 和 opacity 属性有效但因为 50% 关键帧未指定 transform 属性, 所以
animation-timing-function: linear
对它生效至有指定 transform 属性的关键帧, 即 100%
另外, 由于 50% 关键帧未指定
animation-timing-function
, 所以它会使用 .box 元素上指定的 ease 缓动函数
总上所述, 可在关键帧上指定不同的缓动函数, 以满足关键帧间属性的不同变化速率
更强大的 cubic-bezier
细心的读者可能又发现: 缓动函数碰巧是 预定义的关键字, 如果是以下这种情况呢?
显然浏览器预定义的关键字无法表示该类型的缓动函数, 但浏览器提供了强大的 cubic-bezier() 方法翻译过来就是三次贝塞尔曲线因此, 我们可以通过该方法自定义缓动函数
想了解贝塞尔曲线的更多知识, 可阅读 贝塞尔曲线扫盲
AE 时间轴 上呈现的是属性的变化路径, 其未必与变化速率 (即缓动函数) 完全一致因为它们的 X/Y 轴含义不同
如上图所示, AE 是属性随着时间而变, CSS3 animation 是动画进度随着时间而变然而属性的变化是有方向的, 动画进度是永远向前的
举个例子:
AE:
AE 属性变化是有方向的
对应 CSS3
- animation-timing-function
- :
动画进度永远是向前的
如上面二图所示, 下图是上图的速率变化 (缓动函数) 理清 AE 与 CSS3 animation 的对应关系后, 剩下的问题就是: 如何通过 cubic-bezier() 表示图中的 CurveA 与 CurveB
也许有工具可从 AE 直接导出(欢迎读者们提供链接), 但本文为了简单起见, 推荐使用 Ceasar 或 Cubic-Bezier.com 这类可视化工具直接模拟生成
因此, 上述补间动画的缓动函数可表示为:
- @keyframes ae2css {
- 0% {
- animation-timing-function: ease-out;
- }
- 23% {
- animation-timing-function: ease-in;
- }
- 50% {
- animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1.5);
- }
- 76% {
- animation-timing-function: cubic-bezier(0, 0, 0, 1);
- }
- 100% {
- }
- }
总结
本文基于实际案例总结出 AE 到 Web 动画的实现方法相对于无动效稿的反复编码尝试, 该方法无疑能提高效益当然, 手工不能胜任复杂的动画(如 SVG 的变形动画(Morphing)), 并且低效因此, 业界也在机械 / 工具化方面不断推陈出新, 涌现出许多优秀的工具, 让复杂的动画在各终端上得以表现无论如何, 学习更多知识总没错!
最后, 感谢你的阅读!
来源: https://juejin.im/entry/5aa62779f265da238d505bb9