SVG 动画和 CSS 变换:一段复杂的爱情故事

Avatar of Jack Doyle
Jack Doyle 发表

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是 Jack Doyle 的客座文章,他是 GreenSock 动画平台 (GSAP) 的作者。Jack 长期浸淫于网页动画领域,致力于使其更简单、更出色。他之前也曾在这里发表文章,讨论 JavaScript 动画如何成为 性能最佳 的选择(谷歌甚至 推荐 使用它)。这次,他重点介绍了 SVG 动画,以及在使用 CSS 操作它们时可能会遇到的一些非常棘手的问题,以及如何解决这些问题。

SVG 如今非常流行,并且浏览器支持度普遍良好……但有一个明显的例外:CSS 变换。这在动画方面尤其令人头痛,因为缩放、位置、旋转和倾斜都是非常基础的操作。

系好安全带,因为我们将经历一段颠簸的旅程。但不用担心;这个复杂的爱情故事有一个美满的结局。在本文中,我将带您了解一些问题,然后向您展示一种技术,该技术可以协调跨浏览器的行为,并且已内置到最新版本的 GreenSock 动画平台 (GSAP) 中。

浏览器错误和不一致性

首先,查看这些动画 GIF,它们展示了在各种浏览器中(至少截至 2014 年 11 月)对两个 <rect> 元素应用 完全相同的 CSS 动画 的效果。

查看 GreenSock (@GreenSock) 在 CodePen 上创作的 Pen GIF:SVG + CSS 变换问题

  • IEOpera 根本不认可 SVG 元素上的 CSS 变换。相反,您必须将值赋予 transform **属性**。
  • Firefox 不认可基于百分比的原点。
  • Safari 中放大会导致基于百分比和基于像素的原点之间的同步出现问题。
  • Firefox 不识别像 "right bottom" 这样的基于关键字的原点,而 Safari 在缩放比例不是 100% 时会更改它们。
  • 所有浏览器中,基于像素的原点对于 SVG 元素的测量方式与其他 DOM 元素(如下所示)不同。

这是一个演示这些错误的视频。

transform-origin 视为所有旋转和缩放操作围绕的点,就像一个别针固定在那里一样。

我的变换原点在哪里?

通常,如果您想围绕元素的中心进行旋转或缩放,则会设置 transform-origin: 50% 50%。百分比相对于元素自身的原生大小,因此其宽度和高度的 50% 正好落在中间。所有主要浏览器都支持常规 DOM 元素的基于百分比的原点,但只有少数浏览器支持 SVG 元素的基于百分比的原点。

基于像素的值呢?支持度非常好,但还有一个问题:如下所示,SVG 相对于 SVG 的 0,0 坐标来测量基于像素的值,而其他所有 DOM 元素(如 <div>)则相对于其自身的左上角进行测量。因此,如果您想将其居中,则必须进行计算以在 SVG 中绘制坐标(您可以为此使用 getBBox() JS)。因此,您不能对常规 DOM 元素和 SVG 元素应用相同的 CSS 并期望它们的行为相同。

查看 GreenSock (@GreenSock) 在 CodePen 上创作的 Pen SVG 变换原点演示

解决方案

如果我有一个在这里可以使用的纯 CSS 技巧就好了,但由于 IE 和 Opera 完全忽略了 SVG 元素上的 CSS 变换,并且大多数其他浏览器中都存在错误,因此我们将依靠 JavaScript 的一项不可思议的优势:它的灵活性。即使每个浏览器在下周都修复了它们的错误,我们仍然无法回避 SVG 规范处理基于像素的变换原点的方式与其他 DOM 元素完全不同的这一事实,因此 CSS 变换受限于该规范。据我所知,唯一可行的长期解决方案是基于 JS 的解决方案。此外,JS 还为我们提供了许多其他好处,但这将是另一篇文章的主题。

**目标:**能够以与“普通”DOM 元素相同的方式对 SVG 元素的各种变换属性(旋转、缩放、位置和倾斜)进行动画处理,同时在所有主要浏览器中提供相同的效果。我们还应该能够使用标准的 CSS 类值(包括百分比、像素或像 "right bottom" 这样的关键字)来设置变换原点。哦,它必须快速

如果您还不熟悉 GreenSock 的 GSAP,它是一个高性能、专业级的 JavaScript 动画库,具有无与伦比的排序工具、运行时控件和灵活性(可以对 JavaScript 可以触及的任何内容进行动画处理)。谷歌推荐 使用它进行基于 JS 的动画,并且它的速度比 jQuery 快 20 倍。

以下是 GSAP 内部采用的技术概述(我们不会深入研究矩阵数学,因为这超出了本文的范围)。下面的演示说明了当您尝试通过指定 "50% 50%" 的变换原点来围绕其中心旋转 SVG <rect> 时,GSAP 会做什么。

查看 GreenSock (@GreenSock) 在 CodePen 上创作的 Pen SVG 变换原点解决方案

生成的 matrix() 字符串应馈送到元素的 CSS 样式或 transform 属性,具体取决于特定浏览器的需要。

如果您不明白,没关系——所有这些操作在 GSAP 中都是自动为您完成的。

结果

我们不仅获得了跨浏览器的协调行为和 SVG 和非 SVG 元素的一致动画 API,而且我们必须编写的动画代码与(有问题的)纯 CSS 动画相比非常少。

TweenMax.to("#svg, #div", 2, {
  rotation:360, 
  transformOrigin:"50% 50%"
});

这是一个演示了多个原点序列的演示。注意,SVG <rect><div> 的效果都相同。

查看 GreenSock (@GreenSock) 在 CodePen 上创作的 Pen SVG CSS 变换时间轴

使用 GSAP 为 SVG 制作动画的技巧

我不会在这里解释 GSAP 的 API(请参阅 入门文章),但我将提供一些与 SVG 相关的技巧。在 github 上获取 GSAP 或从 GreenSock.com 下载它。

  • 与在 CSS 中将所有变换组件组合到单个“transform”字符串中不同,GSAP 允许您独立定义每个组件,并且您无需担心操作顺序(它始终是一致的)。还要记住,GSAP 使用 xy 来表示 translateX()translateY(),使用 rotation 来表示 rotate()。其他所有内容都相同(scaleXscaleYskewXskewY)。
    #yourID {
        animation-name: myAnimation;
        animation-duration: 2s;
        animation-timing-function: ease-out;
        transform-origin: right bottom;
    }
    @keyframes myAnimation {
        from {transform: none;}
        to {transform: rotate(270deg) scaleX(0.5) translateX(100px);}
    }
    TweenMax.to("#yourID", 2, {
        rotation:270, 
        scaleX:0.5, 
        x:100, 
        transformOrigin:"right bottom"
    });
  • 您可以将原始元素作为目标(第一个参数)传递,也可以传递选择器文本或元素数组。
  • 要对 SVG 的数字属性(而不是 CSS 属性)进行动画处理,请使用 attr:{} 对象,例如
    TweenMax.to("#circle", 2, {attr:{cx:200, cy:300, r:20}, ease:Power2.easeInOut});
  • 您还可以使用3D 属性,例如 zrotationXrotationYperspective,但 IE 和 Opera 等一些浏览器不会识别 SVG 元素的这些属性。
  • 最好通过 GSAP 直接设置 transformOrigin,而不是在 CSS 中设置,因为各种浏览器通过 getComputedStyle() 报告它们的方式不一致。
  • 您可以对几乎任何 CSS 值进行动画处理,包括像 fillstroke 这样的颜色属性。
  • 如果您想进行大量排序,我强烈建议观看 这里这里 的视频。排序是 GSAP 最重要的优势之一。

更多资源