笔记:CSS、canvas 和 SVG 分别实现元素沿环形路径运动动画


 拍摄于 2018 年 5 月 2 日
拍摄于 2018 年 5 月 2 日

CSS 部分

最早接触到使用 CSS 使元素以环形路径运动的办法来自于 Lea Verou 的书《CSS 揭秘》中第八章中的《沿环形路径平移的动画》。虽然随着 CSS 的发展部分语法有些出入,不过作者早在 2012 年就在自己的博客上发表了《Moving an element along a circle》记录相关思路1

演示

以下是一个根据其思路制作的例子,做了一些简单演变,方便演示。

See the Pen CSS animation along circle shape with control by Lien (@movii) on CodePen.

CodePen 链接

理解上面这种办法的关键是去理解当前元素自身的位置,及其配合 transform-origin: x y 来找到一个合适的圆心位置进行旋转运动的过程:

  1. 演示里的 mover(粉红色运动的球体)设置在容器的十二点钟方向,并以其为起点开始运动,所以这个时候要找到圆心:
    1. 就需要把 transform-origin 在 x 轴上值设置为 mover 自身的中心,也就是 50%;
    2. 至于 y 轴上的值,因为现在 mover 自身处在圆的顶部,所以整个圆的半径就正好等于 y 的值(对应下面 CodePen 左边的球体运动);
  2. 稍稍改变一下,mover 的初始位置本身就在圆形的中心,同时希望 mover 以九点位置为起始绕着容器做圆周运动,一个简单的思路,通过绝对定位将元素首先定位到九点方向,之后设定 transform-originx 值正好是半径值 2502 = 125px,y 轴上的值则正好是 mover 的半个身位 302 = 15px(对应下面 CodePen 右边的球体运动)。

圆周运动的起始位置

可以尝试把下面这个 CodePen 中 .moveranimation 声明注释,观察 .mover 本身的起始位置,由此来理解 transform-origin 配合 transform: rotate() 到底做了什么。

See the Pen CSS animation along circle shape by Lien (@movii) on CodePen.

CodePen 链接

canvas 部分

使用画布(<canvas>)制作物体沿环形运动效果,可以分为两个层次进行理解:

  1. 首先是画布制作动画的一般原理,即如何让画布上的内容动起来;
  2. 其次就是和圆这个更加具体的主题打交道,牵扯到一些简单的数学问题如三角函数中正弦(sin)、余弦(cos)。

演示

See the Pen Canvas animation along circular shape by Lien (@movii) on CodePen.

CodePen 链接

canvas 动画的一般原理

使用 <canvas> 元素来实现元素沿环形路径运动,由于 <canvas> 元素本身「画布」的特性,这个过程有点类似「翻书(flip book)」动画:在本子的不同页上画出动画的不同状态,之后以快速翻页造成的视觉差,来达到目标视觉效果。

「翻书动画」,来自 GIPHY

  1. 与「翻书」相同,<canvas> 利用的也是元素状态的快速变化造成的视觉差;
  2. 不同的是「翻书」的「翻页」在 <canvas> 这边变成了擦除(clear)

于是整个动画的过程类似:首先画出第一个状态,完成后将画布擦除干净,之后画出第二个状态,擦除,再画出第三个状态……直到最后一个状态结束,擦除后回到第一个状态,如此循环

理解这些之后,可以得出通过 <canvas> 制作目标动画效果,最基础的三个 API:

  1. 动画本身就是画,在球体运动这个需求本身中更具体一点,需要使用 ctx.arc() 方法(至于圆周运动怎么画,下面会写);
  2. 「擦除」这个操作对应的是 ctx.clearRect()
  3. 控制动画不停执行画、擦除、画、擦除……这个过程,我们需要 requestAnimationFrame() 的帮助。

圆周运动与三角函数

由于是在 <canvas> 这块画布上自己动手去画圆,这里比 CSS 和 SVG 的实现就又会多一步:需要自己去计算圆周,以及在不同状态中运动球体所在的具体位置(坐标轴上的 x 与 y 的值),这就需要使用三角函数相关知识。

三角函数关系图,来自 维基百科

正弦(sin)是对边与斜边的比值,余弦(cos)是邻边与斜边的比值(斜边在这里就是圆的半径值)。

如上图,三角函数关系中有这样几个因素:当前角度(angle),角度相邻的边长(假设为 x),角度相对的边长(假设为 y),以及斜边(圆的半径 r),通过这几个因素可以获得正弦(sin)是对边与斜边的比值 sin(angle)= y/r,余弦(cos)是邻边与斜边的比值 cos(angle)= x/r,分析一下可以得到:

  1. r 为已知量,是我们自己设置的圆的半径长度;
  2. 不论是 sin 还是 cos,JavaScript 本身就已经为我们提供了常亮可以直接使用:Math.sin(angle)Math.cos(angle)
  3. 具体的角度(angle),也可以通过 requestAnimationFrame() 获取:圆周运动本身就是从 0 到 360° 周而往复的过程,所以在每次调用方法的时候,暴露出当前的角度就可以获得需要的值。

帮助理解的伪代码

SVG 部分

和 CSS 不能单单依靠自己来实现动画需要配合 DOM 元素一样,SVG 本身也不能,可以通过两种方法似的 SVG 元素动起来(两种方法的具体语法,以及不同程度上都令人堪忧的兼容性问题,之后会说明2):

  1. 首先介绍的方法中使用另一种标记语言 SMIL(Synchronized Multimedia Integration Language),与其说是 SVG 利用 SMIL 实现动画,不如说是使用 SMIL 其中的标记(tag)来让 SVG 动起来。
  2. 第二种使用的 CSS 中的 offset-path,这个属性看上去有些陌生,因为是新起草用来取而代之废弃了的 motion-path 属性。

使用 SVG 配合 SMIL 中的 animateMotion 和 mpath 元素

See the Pen SVG animation along circular path by Lien (@movii) on CodePen.

CodePen 链接

比起上面 CSS 的方法中通过半径、角度等计算得到的沿环形路径移动的动画,SVG 的方法令人有好感、或者说对比 CSS 方法理解起来更方便的地方是:首先得有一条路径:path,然后声明 mover 去沿着这条路径进行移动。

实现 SVG 沿着环形路径运动的动画的声明如上。

对照上面的源码,需要说明几点

  1. SMIL 的动画中有两个元素相互作用,一个是运动的路径(path),另一个是运动物体:mover;
  2. SMIL 的标记元素中只有一个可以得到元素沿着路径移动的动画,就是 animateMotion:
    1. animateMotion 声明了「需要去沿着一条路径进行运动」,但没有声明运动的路径在哪里,运动时间(dur)、次数(repeatCount)等都可以以属性的形式写在 animateMotion 元素上;
    2. 但是具体的「运动路径」需要使用另一个标记 mpath 声明在 animateMotion 之中;
  3. 直接在 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

See the Pen SVG animation along circular shape by Lien (@movii) on CodePen.

CodePen 链接
  1. keyTimes: 怎么看怎么像是 CSS3 中 @keyframes 的声明,只是 0,10% 或者 100% 在这里使用 0,0.1,和 1 来表示,0% ~ 100% 的变化以 0 ~ 1 这个区间表示;
  2. keyPoints: 对应 keyTimes 声明的每个时间点,声明时间点上运动的状态,不同状态使用分号隔开,在这里顺时针和逆时针分别是 1;0 和 0;1;
  3. 使用 keyTimes 和 keyPoints 来声明动画某一时间点上运动状态的时候,同时需要指定当前 SVG 的 calcMode 属性。测试中 Firefox 不需要单独声明这个属性,而 Safari 和 Chrome 都需要,而且有一点与文档中有出入,文档中说 animateMotion 的 calcMode 默认值是 paced,但在 Chrome 下是无效的,改成了 linear 才有效。

对 SMIL 的简单展开

SMIL 不仅仅可以实现以上的路径动画,还有一个一定见到过的例子:网页中的元素几何形状改变的动画,从 矩形 -> 圆形 -> 三角形 的变换,同时几何体自身的颜色也随着不断改变,就是通过 SVG 配合 SMIL 实现,被称为 SVG Morph4。简单的说除了上面用到的 animateMotion 之外,还有 animateanimateColoranimateTransform 等可以实现动画效果的标记以及很多属性5

SMIL 的兼容问题

从 caniuse 上相关 support 可以看出:

  1. 除了 IE 从来都没有对 SMIL 做过支持,而微软的新儿子 Edge 似乎也完全没有准备对其进行实现
  2. Chrome(Blink)v45 和 Opear v32 已经开始在控制台对于使用 SMIL 的页面进行警告,日后会对其做废弃、不再支持:Intent to deprecate: SMIL,所以使用的时候还是需要谨慎,现在的 Chrome 和 Opera 其中效果即使到位,但不知哪一天就宣布放弃支持。

废弃总会有替代,微软在 issue 里也回复了虽然不会对 SMIL 做实现,但会基于新的标准对 Web Animation APIoffset-path motion-path,而下一部分要介绍另一种实现就是 SVG 配合 offset-path 的办法(Web Animation APi 还没做学习工作,学会了如果可以实现路径动画效果再回来做补充)。

使用 SVG 配合 CSS 中的 offset-path(motion-path)

See the Pen animate SVG along circular path using offset-path by Lien (@movii) on CodePen.

CodePen 链接
属性名的变化

具体可以参考《Motion Path Module Level 1 - Changes》中的说明,offset-path 其实就是 motion-path,只是在演进过程中修改了名字,offset-distance 则对应过去的 motion-offset。

下文的讲解以新的名称 offset-path 和 offset-distance 来说明。

对照上面的源码,需要说明几点

  1. 和 SMIL 中的实现原理大致上相同,CSS 的实现中也分为两个相辅相成的元素,一个是运动的路径:path,另一个是运动物体:mover;
  2. 运动的路径 path 被转移到了 CSS 中进行声明。w3c 的文档里其实对路径的定义给出了很多关键词,比如 angle()、circle()、ellipse() 等,但目前只能使用 path() 关键词;
  3. 具体的动画使用现有的 @keyframes 在不同时间点改变 offset-distance 属性值来实现。offset-path 和 offset-distance 在这里的配合产生的效果很像 SVG 描边动画 中 stroke-dasharray 和 stroke-dashoffset 相互的配合,具体可以参考《笔记:SVG 环形动画(进度条)原理》。

offset-path 的兼容问题

不只是 offset-path,实际上是整个 Motion Path Module 的兼容问题。

  1. Motion Path Module 在各家浏览器的支持非常不好,几乎只有 Chrome 和 Opera 做了支持。而且以 Chrome 为例,version 46+ 到 version 56+ 前后的名称也不同,从 motion-* 改成了 offset-*
  2. 以移动端剔除掉微软系列的角度来看:CSS Motion Path,除去 Blink 的 Chrome 之外,Safari 的 Webkit 也一点动静都没有,连 under consideration 都没赶上6

关于 Motion Path Module,是我想找出来实现元素沿着环形路径运动的可能性有多少的时候,发现虽然觉得是个很不错的动画属性,但兼容实在很差,只能静待日后发展,目前完全不推荐使用。


  1. 这篇《CSS秘密花园: 沿着路径的动画》几乎把提到的《Moving an element along a circle》中的内容一字不差的贴到了网上,可以参考一下。
  2. SVG 部分中没有对 SVG 本身的基本概念和用法做展开,比如 viewportviewbox 代表的意义,或者 <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&gtElements》。
  3. 参考 Stack Overflow 上的问题 svg animation path direction
  4. 关于 SVG Morph,更详细可以参考这篇《An Intro to SVG Animation with SMIL》。
  5. 对于 SMIL 的 animateanimateColoranimateTransform 等属性,CSS-Tricks 的这一篇做了归纳《A Guide to SVG Animations (SMIL) 》。
  6. 参考 WebKit Feature Status

感谢阅读

你们好, 2018 年初把小站从 Jekyll 迁移到 Hugo 的过程中,删除了评论区放的 Disqus 插件,考虑有二:首先无论评论、还是对笔记内容的进一步讨论,读者们更喜欢通过邮件、或者 Twitter 私信的方式来沟通;其次一年多以来 Disqus 后台能看到几乎都是垃圾留言(spam),所以这里直接贴一下邮件、以及 Twitter 账户 地址。

技术发展迭代很快,所以这些笔记内容也有类似新闻的时效性,不免有过时、或者错误的地方,欢迎指正 ^_^。

BEST
Lien(A.K.A 胡椒)
本站总访问量 本站总访客量 本文总阅读量