SMIL 已死!长 live SMIL!SMIL 功能替代方案指南

Avatar of Sarah Drasner
Sarah Drasner

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

SMIL 是 SVG 的原生动画规范,一直备受推崇,因为它为高性能 SVG 动画渲染提供了许多花哨的功能。 不幸的是,WebKit 中对 SMIL 的支持正在减弱,并且微软的 IE 或 Edge 浏览器从未(也不太可能)支持它。 别担心! 我们已经为您准备好了。 本文探讨了其中一些 SMIL 特定功能,并深入研究了实现相同效果的替代方案,这些替代方案具有更长的支持周期。

SMIL 功能:沿路径运动

SMIL 为 SVG 中的逼真运动提供的最引人注目的功能之一是沿路径运动。 现实生活中很少有东西是沿直线运动的,因此沿路径运动使我们能够模拟日常生活中的所见所闻。 以前,您需要将 SVG 路径传递给 animateMotion,并使用 path 和 define 来定义路径数据。 您通过使用 xlink:href=”#thingtoanimate” 指定要动画的元素。

<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" />

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL 运动路径 演示。

替代方案:CSS

幸运的是,沿路径运动模块现在 正在进入 CSS。 目前的支持还很有限 (仅限 Chrome、Opera 和 Android),但 Sara Soueidan 已经提议在 Edge 中采用该模块,并且迄今为止它获得了强有力的支持,在本文发布时已获得超过 420 多票。 请您也加入进来,确保该功能能够发布。 Firefox 的投票 在这里
据我所知,Safari 的支持投票方式更加个性化。 我注册 填写了错误报告,并要求将 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');
}

查看 CodePen 上 CSS-Tricks (@css-tricks) 的 CSS 中的运动路径模块 演示。

我从在 Illustrator 中制作的 SVG 中获取路径数据,然后在 SVGOMG 中进行优化。

在本例中,我想要确保它从起点开始,一直沿着路径移动到路径的终点,您可以在末尾看到一个小的 z,这表示这是一个闭合路径。 这意味着该路径是一个循环,因此这个奇怪的小生物最终会回到起点。 我在关键帧值中设置了这些参数,只指定了 100% 值,因为默认值设置为零:

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

然后在元素上调用动画:

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

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

如果您想要最广泛的当前支持和最灵活的实现,您应该使用 GreenSock。 GSAP 的 Bezier-Plugin(默认情况下包含在 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: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
});

您还可以将类型设置为 thrusoftquadraticcubic。 在 GreenSock API 文档 中可以找到有关这些类型的更多文档。 thru 的一个不错的价值在于它能够影响元素的弯曲度。 如果你将这些点看作是来回弹跳的坐标,那么弯曲度将控制在这些点之间采取的路径的直接程度。 0 表示一条直接路径,1 表示稍微松散一点,2 表示一个不错的曲线,而 3 及更高的值开始在自身上缠绕。

查看 CodePen 上 Sarah Drasner (@sdras) 的 GreenSock Bezier 中的弯曲度演示

最近,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" />

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL 运动路径 演示。

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

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

您还可以通过将对象作为第二个参数传递给该方法并定义 pathDataToBezier 中的 offsetXoffsetY 来偏移这些路径 - 请注意,您可能需要扩展 viewBox,以防止您正在动画的组或属性被裁剪掉。 格式专家:出于可读性的原因,我将对象放在新行上。

// offset the path coordinates by 125px on the x-axis, and 50px on the y-axis:
TweenMax.to("#lil-guy", 3, {
  bezier: {
    values: MorphSVGPlugin.pathDataToBezier("#path", {
      offsetX: 125, 
      offsetY: 50, 
      align: "#lil-guy"
    }),
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL 运动路径 演示。

您甚至可以为这种定位定义矩阵坐标。

// scale the path coordinates up by 1.25 
// and shift it over 120px on the x-axis 
// and up 30px on the y-axis:
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
});

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL 运动路径 演示。

另一种选择是将 align 成员设置为 "relative"。 这会通过使每个坐标的位置相对于 x:0y:0 来保持不变,从而防止它跳跃。 在之前的演示中,我使用 align 来将运动与 #lil-guy 组本身配对。

有关 GreenSocks 的 Bezier 插件 API 中此新功能(指发布本文时的最新功能)的更多信息,请查看他们的 文档,以及这个 精彩的解释视频

SMIL 功能:形状变形

以前,您可以将路径数据作为值传递给 animate 属性,以变形形状。 Noah Blon 有一个很棒的示例。

查看 CodePen 上 Noah Blon (@noahblon) 的 Sitepoint 挑战赛 #1:使用 SVG 和 SMIL 演示。

替代方案:Snap.svg 或 SVG Morpheus

一些库提供了变形路径或形状值,例如 Snap.svgSVG Morpheus,但需要注意的是(即使在 SMIL 中),形状必须具有相同数量的点,否则变形效果会很糟糕,甚至完全失败。 这在预处理阶段令人失望,因为它意味着您必须密切跟踪正在制作的内容,或者与您的设计师良好协作,以确保您获得了此(有时是任意的)中间点数据。 额外的点还会不必要地膨胀您的代码。

替代方案:GreenSock MorphSVG

我强烈推荐 GSAP 的 MorphSVG 插件,因为它可以很好地变形具有不同数量点的形状和路径。 查看本网站徽标上的切换,以了解变形效果的演示。 这里还有另一个示例:

查看 CodePen 上 Sarah Drasner (@sdras) 的 Interchangable Hipster 笔记。

因为 MorphSVG 插件可以对路径数据进行动画处理,所以如果你需要转换形状,可以使用它们的 convertToPath 选项。

MorphSVGPlugin.convertToPath("ellipse"); 
// or circle, rect, etc

这使我们能够进行非常复杂的形状动画处理,对于 web 上的所有动效来说都是一个游戏规则的改变者。

此插件还提供了一些额外的功能,真正让它脱颖而出。第一个是实用程序插件 findShapeIndex。假设你对形状的变形方式不满意(虽然 9 次中有 10 次,自动预设会正常工作)。你加载插件(别担心,你不会添加额外的负担,因为它在生产中不需要),并将两个值传递进去:要进行动画处理的第一个形状的 ID 和第二个形状的 ID。一个 GUI 会弹出,你可以在值之间切换,它还会自动使用 repeat: -1,这样它就会持续地在形状之间循环。

findShapeIndex("#hex", "#star");
// you can comment out above line to automatically disable findShapeIndex() UI

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL motion path 笔记。

一旦你有了这个额外的值,你就可以在 morphSVG 对象中传递 shapeIndex

TweenLite.to("#hex", 1, {morphSVG: { shape: "#star", shapeIndex: 1 }});

第二个额外功能是插件解析剪切路径的能力,这是其他库所不具备的。最后,你还可以重用第一个起始 ID(而不必为了重用而存储路径数据)。值得一提的是,当插件首次发布时,这些功能不可用,但 GreenSock 认识到对支持的需要,因此将其包含在内。

现在我们不再局限于指定的点数,我们扩展了不同类型效果的可能性。下面,我做了一些烟雾。

查看 CodePen 上 Sarah Drasner (@sdras) 的 Where There’s Smoke 笔记。

SMIL 功能:DOM 事件

诸如悬停和点击之类的操作很好地烘焙在 SMIL 中。为了启动,可以指定 begin="click"begin="hover"

<animate 
    xlink:href="#rectblue"
    attributeName="x"
    from="0"
    to="300" 
    dur="1s"
    begin="click"
    values="20; 50"
    keyTimes="0; 1"
    fill="freeze" />

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL motion path 笔记。

另一种方法:JavaScript

有原生 DOM 事件,如 onmouseenteronmouseleave 用于悬停,以及 click 用于点击。你可以使用它们来更改以触发基于 JavaScript 的动画。

另一种方法:JavaScript + CSS

你可以使用 JavaScript 更改类名或直接更改 CSS 样式。这里有一个可能性:更改 animation-play-state 以从事件触发器启动动画。

.st0 {
  animation: moveAcross 1s linear both;
  animation-play-state: paused;
}
@keyframes moveAcross {
  to {
    transform: translateX(100px);
  }
}
document.getElementById("rectblue").addEventListener("click", function() {
  event.target.style.animationPlayState = "running";
});

或在 jQuery 中

$(".st0").on("click", function() {
  $(this).css("animation-play-state", "running");
});

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL motion path 笔记。

此实现不会像 SMIL 示例中那样立即将此动画重置到开头。如果你想实现这一点,CSS-Tricks 上的先前文章 详细介绍了一些不错的执行方法。

另一种方法:Greensock

在 GSAP 中,重启更简单。我们可以将动画添加到时间轴,将其设置为暂停,然后在点击时重启它。这种实现更接近于你对 SMIL 的预期,因为我们不必执行任何像克隆/重新插入 DOM 节点或更改元素上设置的任何属性这样的 hack 操作。

// instantiate a TimelineLite    
var tl = new TimelineLite();

// add a tween to the timeline
tl.to(foo, 0.5, { left: 100 });

$(".st0").on("click", function() {
  tl.restart();
});

SMIL 功能:在 “Y” 完成后运行 “X”

SMIL 还允许更复杂的时间事件,例如 begin="circ-anim.begin + 1s"。这在链接动画时特别有用。

替代方案:CSS

在 CSS 中,我们可以通过在第二个值上设置延迟来链接动画。

.foo {
  animation: foo-move 2s ease both;
}
.bar {
  animation: bar-move 4s 2s ease both; 
  /* the 2 second value corresponds with the length of the iteration of the first. */
}

这种工作方式有点糟糕,因为你必须确保记住更改第一个间隔以及延迟。

另一种方法:CSS 预处理

如果我们在 (例如) Sass 中使用变量,维护和管理这些间隔会更容易。

$secs: 2s;
.foo {
  animation: foo-move $secs ease both;
}
.bar {
  animation: bar-move 4s $secs ease both; 
}

现在我们知道,如果我们更新一个值,它们将保持同步。

但是,如果我们希望始终检测动画何时完成,JavaScript 为此提供了一些不错的原生功能,例如 animationEnd

$("#rectblue").on("animationend", function() {   
  $(this).closest("svg").find("#rectblue2").css("animation-play-state", "running");     
});
#rectblue2 {
  animation: moveAcross 2s 1s ease both;
  animation-play-state: paused;
}

你可能需要点击播放才能看到下面的动画。

查看 CodePen 上 Sarah Drasner (@sdras) 的 SMIL motion path 笔记。

对于延迟,我们会将其烘焙到元素本身的 CSS 的 animation-delay 属性中,如上所示,或者我们可以使用 setTimeout 来表达。

setTimeout(function timeoutHandler() {
  // animation goes here, with any language
}, 1000); // wait for a second

另一种方法:Greensock

我最喜欢的选项是添加对动画时间轴的有限控制。我们可以为此使用 GreenSock 的 TimelineLite,它可以用几种不同的方式表达。
简单时间轴

// instantiate a TimelineLite    
var tl = new TimelineLite();

// add a tween at the beginning of the timeline
tl.to(foo, 0.5, { left: 100 });

// use the position parameter "+=1" to schedule next tween 1 second after the previous tweens end
tl.to(foo, 0.5, { left: 200 }, "+=1");

带相对标签的时间轴

// add a label 0.5 seconds later to mark the placement of the next tween
tl.add("myRelativeLabel")
// use the label to specify an animation a second after the 
tl.to(foo, 0.5, { scale: 0 }, "myRelativeLabel+=1");

// or to use to this label for things like interaction 
tl.play("myRelativeLabel");

我更喜欢相对标签,因为你可以选择一个时间点,在这个时间点上许多事件都会触发或延迟,即使该时间点发生调整,你也不必像在 CSS 中那样进行任何重新计算。

时间轴的好处在于,你可以在一个地方对许多不同的对象进行精细控制,并且可以提供诸如 repeatDelay(多次重复之间的延迟)之类的功能。

SMIL 提供 repeatDur,它允许你指定重复迭代的持续时间(如果你不希望它是默认值,例如 repeatDur="01:30")。在 GreenSock 中,你可以使用 timeScale(n) 加快或减慢时间轴,从而调整重复长度,或者为原本设置 repeatDur="indefinite" 的时间设置 repeat: -1

方便的替换参考表

现在我们已经深入研究了一些最实用的 SMIL 特定功能,值得注意的是,还有许多其他 SMIL 功能的替换方法,你可能已经了解了它们。我们制作了下面的一个小表格,以便快速参考这些更简单的实现。

SMIL 代码 替换代码 替换技术
keyTimes @keyframes CSS
keySplines cubic-bezier CSS
restart restart(); GSAP
calcMode=”discrete” steps() CSS
remove kill();
clear();
clearProps: “all”
GSAP
freeze animation-play-state: paused CSS
freeze pause(); GSAP
fill animation-fill-mode CSS
repeatCount=”indefinite” animation-iteration-count: infinite; CSS
repeatCount=”indefinite” repeat: -1 GSAP
whenNotActive 在 JS 中检测 animation-play-state CSS、原生 JavaScript 或 jQuery
animateMotion path motion-path CSS
animateMotion path bezier GSAP
动画值(路径变形) MorphSVG GSAP
begin=”hover” mouseover、mouseenter/
mouseout、mouseleave
jQuery、原生 JS
begin=”click” click jQuery、原生 JS
begin=”circ-anim.begin + 1s” animation-delay: $vars; SASS
begin=”circ-anim.begin + 1s” 时间轴、位置参数,例如 “+=1” GSAP