以下是 Petr Tichy 的客座文章。 Petr 撰写了许多关于动画、交互和 SVG 的文章,因此我很高兴能邀请他来分享一些他的专业知识,并展示一个如此有趣的演示。 我相信很多人以前都见过这个动画,但它可能感觉有点神奇,超出了我们在网络上可以做到的范围。 就像由视频艺术家/视频编辑软件创建的。 但不,SVG 可以做到这一点。
不久前,Google 公布了其 更新的品牌形象,并在一个带有创意动画的页面上推出了新的标识。
我尝试使用 SVG 和 GreenSock 重现其中一些动画。 在本文中,我将逐步引导您完成我的过程。 希望您能在此过程中有所收获。
这是最终的动画
查看 CodePen 上 Petr TIchy (@ihatetomatoes) 的作品 Google SVG Logo – GreenSock Animation。
在 Illustrator 中设置 SVG
第一步是在 SVG 中创建所有需要动画化的部分。 我在 Illustrator 中描摹了一个 .png 屏幕截图,并创建了一个简单的圆形轮廓。
为了帮助我正确对齐,我将这个圆圈分成 4 个相等的部分。 然后,我创建了 4 个彼此叠加的圆圈和 4 个点,尽量做到精确。

我知道我将使用 GreenSock 进行动画制作,并且我知道使用 DrawSVGPlugin 可以轻松地对圆圈进行动画制作。
为了在“G”的直线和圆圈的顶部之间创建间隙,我创建了一个不可见的白色元素 #gMask
(下图中的 #1 和 #2)。 说实话,这有点丑,但它确实完成了创建最终“G”形状的任务。
直线动画是使用 3 个部分创建的(下图中的 #3 和 #4)。
- A – 带圆角的直线 – 用于线动画的开始
- B – clipPath 蒙版 – 裁剪区域
- C – 直线 – 在动画结束时显示

仍在 Illustrator 中时,我将所有图层分组并将其命名为逻辑名称。 这为我节省了以后在代码编辑器中的时间。 在 HTML 中,对 SVG 元素进行分组和重命名可能是一个相当缓慢的过程。 在 Illustrator 中直接执行此操作要容易得多。
然后,我导出了 SVG 并将其通过 Peter Collingridge 的 SVG 在线编辑器 以减少一些千字节。

整个动画序列可以分解成 3 个部分
- 点的波浪动画
- 点变成字母“G”
- 从“G”回到点
让我们更详细地分解各个部分。
1. 点的波浪动画
这是动画的第一部分,也是最容易创建的部分。
查看 CodePen 上 Petr TIchy (@ihatetomatoes) 的作品 Google SVG Logo – GreenSock Animation。
我们只是使用 GreenSock 的 .to()
方法 对点进行上下动画。
每个点都有自己的时间轴 dotWaveTl
,它将其移动到 y: -7, y: 7
,然后返回到 y: 0
。

然后,所有四个时间轴都以轻微的延迟插入到主时间轴 dotsWaveTl
中。

这会创建所有四个点的波浪效果。
function getDotsWaveTl(){
var dotsWaveTl = new TimelineMax();
$dots.each(function(index, element){
var dotWaveTl = new TimelineMax(),
delay = 0.15;
dotWaveTl
.to(element, 0.4, {y: -7, ease:Power1.easeOut})
.to(element, 0.8, {y: 7, ease:Power1.easeInOut})
.to(element, 0.4, {y: 0, ease:Power1.easeIn})
.to(element, 0.4, {y: -7, ease:Power1.easeOut})
.to(element, 0.8, {y: 7, ease:Power1.easeInOut})
.to(element, 0.4, {y: 0, ease:Power1.easeIn});
dotsWaveTl.add(dotWaveTl, delay*index);
});
return dotsWaveTl;
}
在这个 getDotsWaveTl
函数中,我们循环遍历所有点,为每个点创建一个时间轴,然后将这些时间轴插入到此函数返回的 dotsWaveTl
中。
各个补间动画具有不同的缓动效果,使此波浪感觉更自然。
然后,我们只需将此时间轴添加到主时间轴 tl
中。
/* Main timeline */
tl.add(getDotsWaveTl());
我们将把动画序列的其他部分添加到同一个主时间轴中。
2. 点变成字母“G”
这是迄今为止动画中最难的部分。
查看 CodePen 上 Petr TIchy (@ihatetomatoes) 的作品 Google SVG Logo – GreenSock Animation。
是什么让它如此困难? 有几个原因。 我必须
- 为每个点计算一条路径
- 为每个点计算精确的时间
- 计算直蓝线的时机和动画
- 同步点和线动画

当我们查看代码时,您会看到动画的这部分是使用两个时间轴创建的。
/* Dots rotation */
function getDotsRotateTl(){
var dotsRotateTl = new TimelineMax();
dotsRotateTl
.to($dotRed, 0.9, {bezier:{curviness: 1.5, values: pathRed, ease:Power2.easeInOut}}, 0)
.to($dotYellow, 1.2, {bezier:{curviness: 1, values: pathYellow, ease:Power2.easeInOut}}, 0)
.to($dotGreen, 1.5, {bezier:{curviness: 1, values: pathGreen, ease:Power2.easeInOut}}, 0);
return dotsRotateTl;
}
dotsRotateTl
是一个时间轴,我在其中沿着贝塞尔曲线对红色、黄色和绿色点进行动画处理。 我知道起点在哪里以及我想让点在哪里结束,我只需要计算出中间的点。
pathRed
是一个定义红色点动画路径的点数组。
黄色和绿色点也使用了相同的技术。 我为每个点定义了 4 个点,并计算出了正确的持续时间(黄色为 1.2 秒,绿色点为 1.5 秒)。
第二个时间轴稍微复杂一些。
/* Draw G */
function getDrawGTl(){
var drawGTl = new TimelineMax();
drawGTl
.to($dotBlue, 0.6, {x: 47, ease:Power2.easeIn})
.set($gLineAnim, {autoAlpha: 1, immediateRender: false})
.set($dotBlue, {autoAlpha: 0, immediateRender: false}, '+=0.1')
.from($gLineAnim, 0.8, {x: -120, ease:Power2.easeOut}, '-=0.2')
/* draw red part */
.add('startDrawingG', 1)
.set($gRed, {autoAlpha: 1, immediateRender: false}, 'startDrawingG')
.fromTo($gRed, 0.5, {drawSVG:"71% 88%"}, {drawSVG:"0% 26%", ease:Power1.easeOut}, '-=0.2')
.set($dotRed, {autoAlpha: 0, immediateRender: false}, 'startDrawingG')
/* draw yellow part */
.set($gYellow, {autoAlpha: 1, immediateRender: false}, 'startDrawingG+=0.1')
.fromTo($gYellow, 0.6, {drawSVG:"71% 88%"}, {drawSVG:"17% 36%", ease:Power2.easeOut}, '-=0.45')
.set($dotYellow, {autoAlpha: 0, immediateRender: false}, 'startDrawingG+=0.1')
/* draw green part */
.set($gGreen, {autoAlpha: 1, immediateRender: false}, 'startDrawingG+=0.1')
.fromTo($gGreen, 0.55, {drawSVG:"71% 88%"}, {drawSVG:"36% 61%", ease:Power2.easeOut}, '-=0.6')
.set($dotGreen, {autoAlpha: 0, immediateRender: false}, 'startDrawingG+=0.4')
/* draw blue part */
.set($gLineAnim, {autoAlpha: 0, immediateRender: false}, 'startDrawingG+=0.3')
.set($gLine, {autoAlpha: 1, immediateRender: false}, 'startDrawingG+=0.3')
.set($gBlue, {autoAlpha: 1, immediateRender: false}, 'startDrawingG+=0.3')
.fromTo($gBlue, 0.55, {drawSVG:"71% 88%"}, {drawSVG:"61% 78%", ease:Power2.easeOut}, '-=0.55')
/* draw ending red part */
.set($gRedb, {autoAlpha: 1, immediateRender: false}, 'startDrawingG+=0.25')
.fromTo($gRedb, 0.7, {rotation: '-10', drawSVG:"100% 100%"}, {rotation: '0',drawSVG:"80% 100%", ease:Power2.easeOut}, '-=0.22');
return drawGTl;
}
drawGTl
时间轴控制蓝点的移动。 当它向右移动 47 个像素时,我们隐藏它并显示带圆角右边的线 (A),如 SVG 分解的第二个图像所示。
在 drawGTl
时间轴播放 1 秒后,我添加了 startDrawingG
标签。 这使我能够在正确的时间添加其他补间动画,并通过调整其偏移量来精确控制它们,例如 startDrawingG+=0.3
。
一旦红色点到达交点,我们就隐藏它并显示红色圆圈,然后使用 GreenSock DrawSVGPlugin 对其进行动画处理。
.fromTo($gRed, 0.5, {drawSVG:"71% 88%"}, {drawSVG:"0% 26%", ease:Power1.easeOut}, '-=0.2')
在这一刻,以及在整个演示开发过程中,我大量使用了 jQuery UI 滑块来精确计算正确的数值和时间。
一旦红色段绘制得足够长,我们就对所有后续段进行同样的操作。
所有 71% 88%
段的起点相同,但每个段的终点都不同。
为了使用 DrawSVGPlugin 完成整个字母“G”的绘制,我必须复制红色圆圈并在正确的时间对新的 $gRedb
进行动画处理。
我在 HTML 中直接进行了此更新;无需返回 Illustrator。
.fromTo($gRedb, 0.7, {rotation: '-10', drawSVG:"100% 100%"}, {rotation: '0', drawSVG:"80% 100%", ease:Power2.easeOut}, '-=0.22');
旋转、drawSVG
值、时间和正确的偏移量的组合使我能够创建红色段的无缝动画。

3. 从“G”回到点
返回到点的动画更容易创建。 我使用了与上一个时间轴相同的技术,只是顺序相反。
查看 CodePen 上 Petr TIchy (@ihatetomatoes) 的作品 Google SVG Logo – GreenSock Animation。
我首先使用 DrawSVGPlugin
使彩色段变短,然后开始旋转它们。
当彩色段足够小时,我隐藏它,显示相关的点并将其动画化到起始位置。
以下是时间轴上发生情况的示意图

以下是返回此时间轴的整个函数
/* Back to dots */
function getBackToDotsTl(){
var backToDotsTl = new TimelineMax();
backToDotsTl
/* blue straight line out */
.to($gLineMask, 0.3, {attr: {x: 365}, transformOrigin: 'right center', ease:Power0.easeNone})
.set([$gLineMask, $gLine], {autoAlpha: 0})
/* start moving colored segments (circles) */
.add('rotateG')
.to($gBlue, 0.3, {drawSVG:"56% 78%", ease:Power0.easeNone}, 'rotateG-=0.3')
.to($gGreen, 0.3, {drawSVG:"31% 56%", ease:Power0.easeNone}, 'rotateG-=0.3')
.to($gYellow, 0.3, {drawSVG:"12% 31%", ease:Power0.easeNone}, 'rotateG-=0.3')
.to($gRed, 0.3, {drawSVG:"0% 21%", ease:Power0.easeNone}, 'rotateG-=0.3')
/* start rotating colored segments (circles) */
.add('rotateCircles')
.to([$gBlue, $gRed, $gGreen], 0.4, {rotation:"+=50", transformOrigin: 'center center', ease:Power0.easeNone}, 'rotateCircles')
.to($gGreen, 0.4, {drawSVG:"10% 20%", ease:Power0.easeNone}, 'rotateCircles')
.to($gYellow, 0.4, {rotation:"+=40", transformOrigin: 'center center', drawSVG:"0% 10%", ease:Power0.easeNone}, 'rotateCircles')
.to($gBlue, 0.4, {drawSVG:"50% 60%", ease:Power0.easeNone}, 'rotateCircles')
.to($gRed, 0.1, {drawSVG:"0% 0%", ease:Power0.easeNone}, 'rotateCircles')
.to($gRedb, 0.3, {rotation:"+=50", drawSVG:"80% 90%", ease:Power2.easeInOut}, 'rotateCircles')
/* show red dot */
.set($dotRed, {autoAlpha: 1, x: 60, y: -37}, 'rotateCircles+=0.1')
.set($gRedb, {autoAlpha: 0}, 'rotateCircles+=0.1')
.to($dotRed, 0.9, {bezier:{curviness: 1.5, values: pathRedBack, ease:Power2.easeOut}}, 'rotateCircles+=0.1')
/* show blue dot */
.set($dotBlue, {autoAlpha: 1, x: 51, y: 53}, 'rotateCircles+=0.3')
.set($gBlue, {autoAlpha: 0}, 'rotateCircles+=0.3')
.to($dotBlue, 0.6, {bezier:{curviness: 1.5, values: pathBlueBack, ease:Power2.easeOut}}, 'rotateCircles+=0.3')
/* show yellow dot */
.set($dotYellow, {autoAlpha: 1, x: -5, y: -44}, 'rotateCircles+=0.4')
.set($gYellow, {autoAlpha: 0}, 'rotateCircles+=0.4')
.to($dotYellow, 0.7, {bezier:{curviness: 1.5, values: pathYellowBack, ease:Power2.easeOut}}, 'rotateCircles+=0.4')
/* show green dot */
.set($dotGreen, {autoAlpha: 1, x: -108, y: -56}, 'rotateCircles+=0.4')
.set($gGreen, {autoAlpha: 0}, 'rotateCircles+=0.4')
.to($dotGreen, 0.6, {bezier:{curviness: 1.5, values: pathGreenBack, ease:Power2.easeOut}}, 'rotateCircles+=0.4')
.to($gMask, 0.3, {rotation:"+=60", transformOrigin: '-9 58', ease:Power2.easeInOut}, 'rotateCircles');
return backToDotsTl;
}
我使用了 rotateG
和 rotateCircles
标签来将此时间轴上的大多数补间动画放置在所需的确切位置。
然后,我将此函数包含在主时间轴中,并调整了插入它的确切时间。
/* Main timeline */
tl.add(getDotsWaveTl())
.add(getDotsRotateTl(), '-=0.35')
.add(getDrawGTl(), '-=1.6')
.add(getBackToDotsTl(), '+=2');
tl.timeScale(1.8);
getBackToDotsTl
以 2 秒的延迟 ('+=2')
插入,这使得“G”在动画返回到点之前保持完整 2 秒。
我还通过将 timeScale
设置为 1.8
而不是默认的 1
来调整动画的最终速度。 随意更改此值以查看不同速度下的动画。
最后一个要提到的函数是 init()
。
/* Init */
function init(){
TweenLite.set([$gLine,$circles], {autoAlpha: 0});
TweenLite.set($gBlue, {drawSVG:"61% 78%"}); /* start at 71% 78% */
TweenLite.set($gGreen, {drawSVG:"36% 61%"});
TweenLite.set($gYellow, {drawSVG:"17% 36%"});
TweenLite.set($gRed, {drawSVG:"0% 26%"});
TweenLite.set($gRedb, {drawSVG:"78% 100%", transformOrigin: 'center center'});
TweenLite.set($gLineAnim, {autoAlpha: 0});
}
init();
这是一个我设置所有内容的函数,例如隐藏一些元素,使段具有正确的长度等。
我在一些更复杂的 GSAP 动画中使用了这个函数,例如这个 SVG GreenSock Lab,以便获得我需要的正确起点,因为大多数情况下我都是从 Illustrator 中导出包含所有素材的最终设计或帧。
换句话说,这个函数为动画的开始设置了正确的画布。
为什么我选择使用 GreenSock?
CSS-Tricks 上已经有一些关于 GreenSock 的文章了,所以我假设你可能已经了解它的优势。但是,以下是我选择这个动画库的一些原因:
- GSAP 让你能够精确控制单个补间动画和时间轴的时序和持续时间。
- GSAP API 提供了令人难以置信的灵活的时间轴嵌套功能。
- GSAP 修复了许多在处理 SVG 时出现的跨浏览器不一致问题。
- jQuery UI 滑块允许你浏览 GSAP 时间轴,从而加快开发速度。
- GSAP 让你能够专注于交互式项目的创意部分。
- 使用标签对于更复杂的时间轴来说非常棒。
总结
我知道我动画中的一些代码片段和时间轴可能看起来很复杂,所以请随时在下面的评论中提出任何问题,或在 Twitter 上联系我 @ihatetomatoes。
太棒了
谢谢 iskael!
Petr,
我是你的忠实粉丝,并且注册了你的课程。这里的工作很棒,伙计!
简单来说,非常有价值的文章
太棒了
太棒了
“我大量使用了 jQuery UI 滑块来精确计算正确的数值和时间。”
你能详细说明一下这部分吗?或者在计算正确值时向我们展示你的设置?
嗨,Timo,我使用滑块快速在时间轴上滑动到正确的时间,稍微调整了数值,然后再次使用它。
无需从头观看时间轴,你可以快速移动到正确的时刻,这在处理较长时间轴时可以节省大量时间。
我可能会在我的 YouTube 频道上创建一个简短的屏幕录制,这有帮助吗?
嗨,Petr,
很棒的作品,一如既往!!
你真棒!! :)
很棒的东西,Petr,
我看到你在你的网站上有一系列关于 GreenSock 的文章。
感谢大家的宝贵反馈。
这真是太酷了,伙计!!爱你,伙计!真是太棒了!太棒了!
谢谢 Danish,看起来你玩 Codepen 演示玩得很开心。
很棒的教程,Petr,谢谢!
感谢查看,Antonella!你尝试过用 GreenSock 制作 SVG 动画吗?