👴🏻 SMIL 驾鹤西去,万寿无疆!

SMIL,SVG 的原生动画规范,曾经风光无限,凭借着其强大的功能和高效的渲染能力,在 SVG 动画领域呼风唤雨。然而,时过境迁,SMIL 的支持在 WebKit 中日渐式微,而微软的 IE 和 Edge 浏览器更是从未支持过 SMIL,也几乎不可能在未来支持。

别担心!我们今天就来探讨一些 SMIL 特有的功能,并深入研究如何用其他方法来实现相同的效果,以确保你的动画能拥有更广泛的浏览器兼容性。

🏃🏻‍♀️ 沿着路径运动

SMIL 最吸引人的地方之一就是它能够让 SVG 对象沿着路径运动,从而实现更加逼真的动画效果。毕竟,现实生活中很少有物体是沿着直线运动的,沿着路径运动可以让我们模拟现实生活中的各种运动轨迹。

在过去,你需要将 SVG 路径数据传递给 animateMotion 元素,并使用 path 属性来定义路径数据。然后,你可以通过 xlink:href 属性来指定要进行动画的元素。

<animateMotion 
  xlink:href="#lil-guy" 
  dur="3s" 
  repeatCount="indefinite" 
  fill="freeze" 
  path="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" />

替代方案:CSS

幸运的是,现在 CSS 也支持沿着路径运动的功能了!虽然目前支持的浏览器还不多(仅限于 Chrome、Opera 和 Android),但 Sara Soueidan 已经提议在 Edge 中加入该功能,并且得到了强烈的支持,在本文发布时已经获得了超过 420 票。请加入我们,一起呼吁该功能早日实现!Firefox 的投票页面 在这里

至于 Safari,据我所知,它的支持情况可能需要单独处理。我已经注册了一个 bug 报告,并请求在 CSS 中添加沿着路径运动的功能。

为了在 CSS 中使用沿着路径运动,你需要将路径数据传递给 offset-path 属性,就像这样:

.move-me {
  offset-path: path('M3.9,74.8c0,0,0-106.4,75.5-42.6S271.8,184,252.9,106.9s-47.4-130.9-58.2-92s59.8,111.2-32.9,126.1 S5.9,138.6,3.9,74.8z');
}

我通常会在 Illustrator 中创建 SVG,然后使用 SVGOMG 进行优化,以获取路径数据。

在这个例子中,我希望动画对象沿着路径从起点运动到终点,并且路径是一个闭合路径,因为路径数据末尾有一个 z。这意味着路径是一个循环,所以这个小生物最终会回到起点。我在关键帧中设置了这些参数,只指定了 100% 的值,因为默认值为 0

@keyframes motionpathguy {
  100% {
    motion-offset: 100%;
  }
}

然后,将动画应用于元素:

.move-me {
  animation: motionpathguy 10s linear infinite both;
}

替代方案:GreenSock 的沿着路径运动

如果你想要最广泛的浏览器支持和最灵活的实现方式,那么你应该使用 GreenSock。GSAP 的 Bezier 插件(默认情况下包含在 TweenMax 中)支持 IE7 及更高版本(非 SVG 元素),以及 IE9 及更高版本(SVG 元素),这是目前最广泛的 SVG 动画支持。它在移动设备上也运行得很好。

我之前在 David Walsh 博客上 写过关于这个插件的文章,但这里只是简要回顾一下,并介绍一些自那以后发布的新功能:

最初,你需要传递一个值数组:

bezier: {
  type: "soft",
  values:[{x:10, y:30}, {x:-30, y:20}, {x:-40, y:10}, {x:30, y:20}, {x:10, y:30}],
  autoRotate: true
}

但正如你所看到的,你还可以选择自动旋转(或不旋转),就像 SMIL 的 rotate 属性一样。如果你想使用 SMIL 中的 auto-reverseauto:n 参数来指定旋转的初始位置或旋转角度,GSAP 允许你使用 rotation:90 来更改旋转角度,或者如果你需要更精细的控制,可以使用更具体的设置:

autorotate: [
  first position property, like "x",
  second position property, like "y",
  rotation property, typically "rotation" but can be “rotationY”,
  integer for radians/degrees the rotation starts from like 10,
  boolean for radians or degrees- radians is true
]

在 SMIL 中,你可以对路径或组进行变换,以改变动画对象在运动过程中的方向。在 GSAP 中,你可以通过 autoRotate: false 轻松实现这一点,并使用 set 初始化旋转。你也可以像在 SMIL 中一样,在 SVG 属性本身对元素进行变换,但这有点不太优雅,而且在工作时更难跟踪。

TweenMax.set("#foo" {
  rotation: 90 // or whatever number
});

你还可以将 type 属性设置为 thrusoftquadraticcubic。有关这些属性的更多文档,请查看 GreenSock API 文档thru 属性的一个很好的用途是能够影响元素的弯曲程度。如果你将这些点视为弹跳的坐标,那么弯曲程度将控制在这些点之间采取的路径的直接程度。0 表示直线路径,1 表示稍微松散的路径,2 表示一个漂亮的曲线,而 3 及更高的值将开始在自身上缠绕。

graph LR
  subgraph 0
    A[0]
  end
  subgraph 1
    B[1]
  end
  subgraph 2
    C[2]
  end
  subgraph 3
    D[3]
  end
  A --> B
  B --> C
  C --> D

最近,GreenSock 也提供了将路径数据传递给 CSS 和 SMIL 模块的能力,就像使用原生 SMIL 一样。这作为他们 MorphSVG 插件的扩展,因此你需要添加该插件,并像这样使用它:

TweenMax.to("#lil-guy", 3, {
  bezier: {
    MorphSVGPlugin.pathDataToBezier("#path", {align: "#lil-guy" }), 
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});
<path id="path" d="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" fill="none" />

默认情况下,会将我要进行动画的组(在本例中为 #lil-guy)的左上角与路径轨迹对齐。这会导致视觉上的错位。因此,我使用 TweenLite.set#lil-guy 设置为使用中心点:

TweenLite.set("#lil-guy", {xPercent:-50, yPercent:-50});

你还可以通过将一个对象作为该方法的第二个参数传递,并在 pathDataToBezier 中定义 offsetXoffsetY 来偏移这些路径,注意,你可能需要扩展 viewBox,以确保你正在进行动画的组或属性不会被裁剪掉。

// 将路径坐标在 x 轴上偏移 125px,在 y 轴上偏移 50px:
TweenMax.to("#lil-guy", 3, {
  bezier: {
    values: MorphSVGPlugin.pathDataToBezier("#path", {
      offsetX: 125, 
      offsetY: 50, 
      align: "#lil-guy"
    }),
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});

你甚至可以为这个定位定义一个矩阵坐标。

// 将路径坐标放大 1.25 倍
// 并将其在 x 轴上偏移 120px
// 在 y 轴上偏移 30px:
TweenMax.to("#lil-guy", 3, {
  bezier: {
    values: MorphSVGPlugin.pathDataToBezier("#path", {
      matrix:[1.5,0,0,1.5,120,-30], 
      align:"lil-guy"}),
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});

另一个选择是将 align 属性设置为 “relative”。这将防止动画对象跳跃,因为它会保持每个坐标相对于 x:0y:0 的位置。在之前的示例中,我使用 align 将运动与 #lil-guy 组本身配对。

有关 GreenSock 的 Bezier 插件 API 中这个新功能(新是指在本文发布之日新发布的功能!)的更多信息,请查看他们的 文档,以及这个 很棒的解释视频

🎭 形状变形

以前,你可以将路径数据作为值传递给 animate 属性,以使形状变形。Noah Blon 有一个很好的例子: