CSS 部分
最早接触到使用 CSS 使元素以环形路径运动的办法来自于 Lea Verou 的书《CSS 揭秘》中第八章中的《沿环形路径平移的动画》。虽然随着 CSS 的发展部分语法有些出入,不过作者早在 2012 年就在自己的博客上发表了《Moving an element along a circle》记录相关思路1。
演示
以下是一个根据其思路制作的例子,做了一些简单演变,方便演示。
理解上面这种办法的关键是去理解当前元素自身的位置,及其配合 transform-origin: x y
来找到一个合适的圆心位置进行旋转运动的过程:
- 演示里的 mover(粉红色运动的球体)设置在容器的十二点钟方向,并以其为起点开始运动,所以这个时候要找到圆心:
- 就需要把
transform-origin
在 x 轴上值设置为 mover 自身的中心,也就是 50%; - 至于 y 轴上的值,因为现在 mover 自身处在圆的顶部,所以整个圆的半径就正好等于
y
的值(对应下面 CodePen 左边的球体运动); - 稍稍改变一下,mover 的初始位置本身就在圆形的中心,同时希望 mover 以九点位置为起始绕着容器做圆周运动,一个简单的思路,通过绝对定位将元素首先定位到九点方向,之后设定
transform-origin
,x
值正好是半径值 250/2 = 125px,y
轴上的值则正好是 mover 的半个身位 30/2 = 15px(对应下面 CodePen 右边的球体运动)。
圆周运动的起始位置
可以尝试把下面这个 CodePen 中 .mover
的 animation
声明注释,观察 .mover
本身的起始位置,由此来理解 transform-origin
配合 transform: rotate()
到底做了什么。
canvas 部分
使用画布(<canvas>
)制作物体沿环形运动效果,可以分为两个层次进行理解:
- 首先是画布制作动画的一般原理,即如何让画布上的内容动起来;
- 其次就是和圆这个更加具体的主题打交道,牵扯到一些简单的数学问题如三角函数中正弦(
sin
)、余弦(cos
)。
演示
canvas 动画的一般原理
使用 <canvas>
元素来实现元素沿环形路径运动,由于 <canvas>
元素本身「画布」的特性,这个过程有点类似「翻书(flip book)」动画:在本子的不同页上画出动画的不同状态,之后以快速翻页造成的视觉差,来达到目标视觉效果。
- 与「翻书」相同,
<canvas>
利用的也是元素状态的快速变化造成的视觉差; - 不同的是「翻书」的「翻页」在
<canvas>
这边变成了擦除(clear)。
于是整个动画的过程类似:首先画出第一个状态,完成后将画布擦除干净,之后画出第二个状态,擦除,再画出第三个状态……直到最后一个状态结束,擦除后回到第一个状态,如此循环。
理解这些之后,可以得出通过 <canvas>
制作目标动画效果,最基础的三个 API:
- 动画本身就是画,在球体运动这个需求本身中更具体一点,需要使用
ctx.arc()
方法(至于圆周运动怎么画,下面会写); - 「擦除」这个操作对应的是
ctx.clearRect()
; - 控制动画不停执行画、擦除、画、擦除……这个过程,我们需要
requestAnimationFrame()
的帮助。
圆周运动与三角函数
由于是在 <canvas>
这块画布上自己动手去画圆,这里比 CSS 和 SVG 的实现就又会多一步:需要自己去计算圆周,以及在不同状态中运动球体所在的具体位置(坐标轴上的 x 与 y 的值),这就需要使用三角函数相关知识。
如上图,三角函数关系中有这样几个因素:当前角度(angle),角度相邻的边长(假设为 x
),角度相对的边长(假设为 y
),以及斜边(圆的半径 r
),通过这几个因素可以获得正弦(sin)是对边与斜边的比值 sin(angle)= y/r
,余弦(cos)是邻边与斜边的比值 cos(angle)= x/r
,分析一下可以得到:
r
为已知量,是我们自己设置的圆的半径长度;- 不论是
sin
还是cos
,JavaScript 本身就已经为我们提供了常亮可以直接使用:Math.sin(angle)
和Math.cos(angle)
; - 具体的角度(angle),也可以通过
requestAnimationFrame()
获取:圆周运动本身就是从 0 到 360° 周而往复的过程,所以在每次调用方法的时候,暴露出当前的角度就可以获得需要的值。
SVG 部分
和 CSS 不能单单依靠自己来实现动画需要配合 DOM 元素一样,SVG 本身也不能,可以通过两种方法似的 SVG 元素动起来(两种方法的具体语法,以及不同程度上都令人堪忧的兼容性问题,之后会说明2):
- 首先介绍的方法中使用另一种标记语言 SMIL(Synchronized Multimedia Integration Language),与其说是 SVG 利用 SMIL 实现动画,不如说是使用 SMIL 其中的标记(tag)来让 SVG 动起来。
- 第二种使用的 CSS 中的
offset-path
,这个属性看上去有些陌生,因为是新起草用来取而代之废弃了的motion-path
属性。
使用 SVG 配合 SMIL 中的 animateMotion 和 mpath 元素
比起上面 CSS 的方法中通过半径、角度等计算得到的沿环形路径移动的动画,SVG 的方法令人有好感、或者说对比 CSS 方法理解起来更方便的地方是:首先得有一条路径:path,然后声明 mover 去沿着这条路径进行移动。
对照上面的源码,需要说明几点
- SMIL 的动画中有两个元素相互作用,一个是运动的路径(path),另一个是运动物体:mover;
- SMIL 的标记元素中只有一个可以得到元素沿着路径移动的动画,就是 animateMotion:
- animateMotion 声明了「需要去沿着一条路径进行运动」,但没有声明运动的路径在哪里,运动时间(dur)、次数(repeatCount)等都可以以属性的形式写在 animateMotion 元素上;
- 但是具体的「运动路径」需要使用另一个标记 mpath 声明在 animateMotion 之中;
- 直接在 HTML 中使用 SVG 环形元素的时候,不需要使用 path 元素而使用一个 shortcut 的 circle 元素来声明,但在这里由于 animateMotion 中 mpath 的限制,不能直接声明「运动的路径」为 circle 元素,需要将 circle 转换为 path 元素;
SVG 中将 circle 或者 ellipse 元素转换成 path 元素,可以使用这个在线工具:SVG Circle/Ellipse to Path Converter 。
动画运动方向的控制
update 2017-08-19
上面那个 demo 的运动方向和 CSS 方法中的不同,也许是默认的行为(没有搜索到相关参考),但 SMIL 提供了属性 keyTimes 和 keyPoints,结合在一起使用:可以变相控制这里动画运动的方向:顺时针 v.s. 逆时针[^3]。- keyTimes: 怎么看怎么像是 CSS3 中 @keyframes 的声明,只是 0,10% 或者 100% 在这里使用 0,0.1,和 1 来表示,0% ~ 100% 的变化以 0 ~ 1 这个区间表示;
- keyPoints: 对应 keyTimes 声明的每个时间点,声明时间点上运动的状态,不同状态使用分号隔开,在这里顺时针和逆时针分别是 1;0 和 0;1;
- 使用 keyTimes 和 keyPoints 来声明动画某一时间点上运动状态的时候,同时需要指定当前 SVG 的 calcMode 属性。测试中 Firefox 不需要单独声明这个属性,而 Safari 和 Chrome 都需要,而且有一点与文档中有出入,文档中说 animateMotion 的 calcMode 默认值是 paced,但在 Chrome 下是无效的,改成了 linear 才有效。
对 SMIL 的简单展开
SMIL 不仅仅可以实现以上的路径动画,还有一个一定见到过的例子:网页中的元素几何形状改变的动画,从 矩形 -> 圆形 -> 三角形 的变换,同时几何体自身的颜色也随着不断改变,就是通过 SVG 配合 SMIL 实现,被称为 SVG Morph3。简单的说除了上面用到的 animateMotion
之外,还有 animate
、animateColor
、animateTransform
等可以实现动画效果的标记以及很多属性4。
SMIL 的兼容问题
从 caniuse 上相关 support 可以看出:
- 除了 IE 从来都没有对 SMIL 做过支持,而微软的新儿子 Edge 似乎也完全没有准备对其进行实现。
- Chrome(Blink)v45 和 Opear v32 已经开始在控制台对于使用 SMIL 的页面进行警告,日后会对其做废弃、不再支持:Intent to deprecate: SMIL,所以使用的时候还是需要谨慎,现在的 Chrome 和 Opera 其中效果即使到位,但不知哪一天就宣布放弃支持。
废弃总会有替代,微软在 issue 里也回复了虽然不会对 SMIL 做实现,但会基于新的标准对 Web Animation API 和 offset-path motion-path,而下一部分要介绍另一种实现就是 SVG 配合 offset-path 的办法(Web Animation APi 还没做学习工作,学会了如果可以实现路径动画效果再回来做补充)。
使用 SVG 配合 CSS 中的 offset-path(motion-path)
属性名的变化
具体可以参考《Motion Path Module Level 1 - Changes》中的说明,offset-path 其实就是 motion-path,只是在演进过程中修改了名字,offset-distance 则对应过去的 motion-offset。
下文的讲解以新的名称 offset-path 和 offset-distance 来说明。
对照上面的源码,需要说明几点
- 和 SMIL 中的实现原理大致上相同,CSS 的实现中也分为两个相辅相成的元素,一个是运动的路径:path,另一个是运动物体:mover;
- 运动的路径 path 被转移到了 CSS 中进行声明。w3c 的文档里其实对路径的定义给出了很多关键词,比如 angle()、circle()、ellipse() 等,但目前只能使用 path() 关键词;
- 具体的动画使用现有的 @keyframes 在不同时间点改变 offset-distance 属性值来实现。offset-path 和 offset-distance 在这里的配合产生的效果很像 SVG 描边动画 中 stroke-dasharray 和 stroke-dashoffset 相互的配合,具体可以参考《笔记:SVG 环形动画(进度条)原理》。
offset-path 的兼容问题
不只是 offset-path
,实际上是整个 Motion Path Module 的兼容问题。
- Motion Path Module 在各家浏览器的支持非常不好,几乎只有 Chrome 和 Opera 做了支持。而且以 Chrome 为例,version 46+ 到 version 56+ 前后的名称也不同,从
motion-*
改成了offset-*
; - 以移动端剔除掉微软系列的角度来看:CSS Motion Path,除去 Blink 的 Chrome 之外,Safari 的 Webkit 也一点动静都没有,连 under consideration 都没赶上5。
关于 Motion Path Module,是我想找出来实现元素沿着环形路径运动的可能性有多少的时候,发现虽然觉得是个很不错的动画属性,但兼容实在很差,只能静待日后发展,目前完全不推荐使用。
这篇《CSS秘密花园: 沿着路径的动画》几乎把提到的《Moving an element along a circle》中的内容一字不差的贴到了网上,可以参考一下。 ↩︎
SVG 部分中没有对 SVG 本身的基本概念和用法做展开,比如
viewport
、viewbox
代表的意义,或者 <g>、<use>、<defs>、<symbol> 等元素的使用,需要可以参考这些:《SVG 快速入门 - 基本概念》、《Understanding SVG Coordinate Systems and Transformations (Part 1) — The viewport, viewBox, and preserveAspectRatio》、《Understanding SVG Coordinate Systems and Transformations (Part 2) — The transform Attribute》、《Understanding SVG Coordinate Systems and Transformations (Part 3) — Establishing New Viewports》、《Structuring, Grouping, and Referencing in SVG — The<g>、<use>、<defs> and <symbol>Elements》。 ↩︎关于 SVG Morph,更详细可以参考这篇《An Intro to SVG Animation with SMIL》。 ↩︎
对于 SMIL 的
animate
、animateColor
、animateTransform
等属性,CSS-Tricks 的这一篇做了归纳《A Guide to SVG Animations (SMIL) 》。 ↩︎
技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。
BEST
Lien(A.K.A 胡椒)