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
});
您还可以将类型设置为 thru
、soft
、quadratic
或 cubic
。 在 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
中的 offsetX
和 offsetY
来偏移这些路径 - 请注意,您可能需要扩展 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:0
、y:0
来保持不变,从而防止它跳跃。 在之前的演示中,我使用 align
来将运动与 #lil-guy
组本身配对。
有关 GreenSocks 的 Bezier 插件 API 中此新功能(指发布本文时的最新功能)的更多信息,请查看他们的 文档,以及这个 精彩的解释视频。
SMIL 功能:形状变形
以前,您可以将路径数据作为值传递给 animate 属性,以变形形状。 Noah Blon 有一个很棒的示例。
查看 CodePen 上 Noah Blon (@noahblon) 的 Sitepoint 挑战赛 #1:使用 SVG 和 SMIL 演示。
替代方案:Snap.svg 或 SVG Morpheus
一些库提供了变形路径或形状值,例如 Snap.svg 和 SVG 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 事件,如 onmouseenter
和 onmouseleave
用于悬停,以及 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 |
不想使用 JavaScript 或复杂操作,只想简单地从 0 到 1 进行缩放,持续时间为 10 秒,使用默认的 x 和 y 坐标。
如果你访问 http://svgdesign.guru,你会看到乌龟使用这种简单的缩放效果飞入屏幕。有什么比 SMIL 更好的选择吗?我找不到替代方案。
可以使用简单的 CSS 转换(缩放和平移)在关键帧动画中实现相同的效果。
我最近做了一个实验性的笔和文章,使用原生 js 版本的形状变形,
文章:http://codepen.io/vidhill/post/svg-morph-without-smil
笔记:http://codepen.io/vidhill/pen/MKWRjo
基于我对如何不使用额外库(如果你还没有使用)的情况下重现它的思考
它受制于相同的点数限制
史蒂文;展示你的 CSS 方法,然后解释为什么它比使用一行声明式 SMIL 更好。
那个“网站”太令人尴尬了,尤其是关于 Chrome 的长篇大论。
使用 flexbox、viewport-size 等等,而不是 position absolute。
为了帮助你的乌龟
你确实用那个乌龟做了一个简单的动画。
Yimi;
感谢你告诉我 flexbox 允许可扩展的 HTML 文本和对文档结构中 CSS div 的像素级定位!我不知道这一点。
也不知道缩放被认为是动画。
嗨,大卫·希尔,
我真的很喜欢你的 SVG Morph 示例。超级酷,我已经把它加书签了。我确实想稍微强调一下,尽管你的示例很棒,但它有一个非常简单的变形。GSAP 提供的铃铛和口哨与更复杂的路径形状变形(带有不均匀的点,再次看看烟雾演示以了解我的意思)以及非常简化的代码相一致。能够在不同的锚点之间切换以精细控制变形也非常棒,尤其是在时间紧迫时,你不想重写大量的 JS 才能获得稍微不同的效果。很棒的演示,还有很好的帖子。
谢谢莎拉,我真的很需要这样的文章。在遇到问题时,除了 Webkit 浏览器之外,我还要实现一组跨兼容的 SMIL 技术,我不确定该走哪条路(GreenSock 与 JS polyfill)。
向你和莎拉·苏埃丹致敬。
有一个问题——GreenSock 通常会收取使用某些高级插件(如 Morph)的许可费。随着更多采用或公开请愿,是否有迹象表明这种情况会有所改变?
杰克·多伊尔和卡尔·舒夫是维护 Greensock 的人。他们没有其他日常工作。Greensock 是他们唯一做的事情。他们倾注了全部心血和灵魂在这个库上。我认为免费版本提供的功能非常强大。高级插件是他们维持库的唯一收入来源。你基本上是在要求他们免费工作。你免费工作吗?
感谢保罗·凯恩!另外,我同意艾略特·杰诺——你肯定得到了你付出的,GreenSock 团队非常值得这笔钱。
我会一直保持这个开放状态,直到我完成将我的游戏 esviji 从 SMIL 迁移到其他任何东西……谢谢!
不客气!很高兴它对你有用。
很棒的帖子,莎拉,幸运的是我从来没有用过 SMIL。我一直是 GSAP 的长期用户,主要是因为它出色的浏览器支持和简单的工作流程。
很棒的文章,莎拉,有很多替代方案。
一如既往,很棒的文章,莎拉。我与 SMIL 的短暂约会很尴尬,我坐在餐厅的桌子对面盯着它,等待它说出一些惊人的话。它没有。我的汤都凉了。我回家了,启动了 GSAP,吃了一个方便面。
哈哈,是的,我在接触 GSAP 之前主要使用 SMIL。这就是为什么我对这个库感到如此惊喜的部分原因。