在省略号路径上使用@keyframes 动画 svg
Animate svg with @keyframes on an elipsis path
正如标题所说,我正在尝试为一个 svg 制作动画,使其跨越看起来像省略号的路径以匹配它
第一个图标(无限符号)。我试过使用变换,但运动很糟糕。我怎样才能顺利(尽可能地)实现这一目标?
我找到了一些关于类似内容的文章,但它并没有让我得到我想要的平滑省略号:
https://www.useragentman.com/blog/2013/03/03/animating-circular-paths-using-css3-transitions/
http://thenewcode.com/860/Animating-Elements-In-Arcs-Circles-and-Ellipses-With-CSS
https://usefulangle.com/post/32/moving-an-element-in-circular-path-with-css
这里是link来一支笔https://codepen.io/acubaniti/pen/abNqzEV?editors=1100
代码如下:
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
animation: phone-orbits-cw 6s cubic-bezier(0.5, 0, 0.38, 0.1) infinite;
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
will-change: transform, opacity;
transform-style: preserve-3d;
transform-origin: center top;
}
#left, #right {
fill: none;
stroke-width: 10;
stroke-miterlimit: 10;
stroke: white;
transform-origin: 200px 150px;
animation-fill-mode: forwards;
stroke-dasharray: 314;
stroke-dashoffset: 314;
opacity: 0;
}
#right {
animation: circle-fill 6s linear infinite;
stroke: white;
transform: rotate(-180deg);
}
#left {
animation: circle-fill-left 6s linear infinite;
stroke: white;
transform: rotateX(180deg);
}
@keyframes circle-fill {
0%, 12.5% {
stroke-dashoffset: 314;
opacity: 1;
}
12.5%, 100% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
}
}
@keyframes circle-fill-left {
12.5% {
stroke-dashoffset: 314;
opacity: 1;
}
25% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
stroke-dashoffset: 0;
}
}
@keyframes phone-orbits-cw {
0% {
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
}
45% {
opacity: 0;
transform: rotate3d(0, 0, 1, -360deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 360deg);
}
58.75% {
opacity: 1;
transform: rotate3d(0, 0, 1, -360deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 360deg);
}
72.5% {
opacity: 1;
}
86.25% {
opacity: 1;
transform: rotate3d(0, 0, 1, 360deg) translate3d(-2%, -5%, 0) rotate3d(0, 0, 1, -360deg);
}
100% {
opacity: 1;
transform: rotate3d(0, 0, 1, -360deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, 360deg);
}
}
@keyframes phone-orbits-ccw {
0% {
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
}
40% {
opacity: 0;
transform: rotate3d(0, 0, 1, 180deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, -180deg);
}
45% {
opacity: 1;
}
50% {
opacity: 1;
transform: rotate3d(0, 0, 1, -180deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 180deg);
}
75% {
opacity: 1;
}
100% {
opacity: 1;
transform: rotate3d(0, 0, 1, 180deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, -180deg);
}
}
<html>
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300" xml:space="preserve">
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
</g>
<circle id="right" cx="200" cy="150" r="50"/>
<circle id="left" cx="100" cy="150" r="50"/>
</svg>
</body>
</html>
谢谢。
如果您可以在浏览器兼容性方面做出妥协,您应该能够使用 motion path 以更加语义化的方式做到这一点。它需要 Chrome 64、Edge 79、Firefox 72、Opera 45,但不支持 Safari。
以下示例以多种方式简化了您的代码。首先,我将这两个圆圈重写为一条路径,并另外移动它,使路径的起点和终点(“无穷大”符号的 mid-point)位于坐标系的原点。这是因为这条路径将被重用为运动路径——而不是引用,不幸的是,如果你继续使用 CSS,那是不可能的,但你可以只 copy-and-paste 路径命令。
为了让 stroke-dashoffset
动画更轻松一些,路径也获得了属性(不是 CSS 属性!)pathLength="100"
。这基本上是一个任意值,意味着:对于所有 distance-along-a-path 计算, 就好像 100 是路径长度。
其次,我使用相同的路径命令定义了一个 offset-path
。 offset-rotate
设置为 0deg
以避免 phone 在移动时沿路径切线旋转。 offset-distance
在动画期间从 0% 增加到 100%。
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
animation: phone-orbit 6s ease-in-out infinite;
opacity: 0;
offset-path: path("M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0");
offset-rotate: 0deg;
offset-distance: 0;
will-change: transform, opacity;
}
#infinity {
fill: none;
stroke-width: 6.666;
stroke: white;
animation-fill-mode: forwards;
stroke-dasharray: 100;
stroke-dashoffset: 100;
opacity: 0;
animation: infinity-fill 6s linear infinite;
}
@keyframes infinity-fill {
0%, 25% {
stroke-dashoffset: 100;
opacity: 1;
}
25%, 100% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
}
}
@keyframes phone-orbit {
0% {
opacity: 0;
}
40% {
opacity: 0;
}
45% {
opacity: 1;
}
50% {
offset-distance: 0%;
}
100% {
opacity: 1;
offset-distance: 100%;
}
}
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300" xml:space="preserve">
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
</g>
<path id="infinity" transform="translate(150 150) scale(1.666)" d="M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0" pathLength="100"/>
</svg>
</body>
为了获得更好的支持,您可以切换到 in-markup SMIL animation。只有旧的 IE 版本会让你失望。
主要区别在于不同动画的启动方式:除了第一个 infinityFill
动画在 0 秒启动外,每个后续动画的启动都与其运行时间有关。特别是 phoneOrbit
动画在 infinityFill
动画结束时开始,然后 phoneOrbit
结束再次开始 infinityFill
,以此类推。
此外,运动路径可以真正重复使用。
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
opacity: 0;
will-change: transform, opacity;
}
#infinity {
fill: none;
stroke-width: 10;
stroke: white;
stroke-dasharray: 100;
opacity: 0;
}
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300">
<defs>
<path id="motion" d="M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0" pathLength="100"/>
</defs>
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
<animateMotion id="phoneOrbit" dur="3s"
begin="infinityFill.end" rotate="0">
<mpath href="#motion" />
</animateMotion>
<animate attributeName="opacity" dur="6s"
begin="infinityFill.begin"
values="0;0;1;1" keyTimes="0;.45;.5;1" />
</g>
<use id="infinity" href="#motion" transform="translate(150 150) scale(1.666)">
<animate id="infinityFill" attributeName="stroke-dashoffset"
dur="3s" begin="0s;phoneOrbit.end"
values="100;0;0" keyTimes="0;.5;1" />
<animate attributeName="opacity" dur="3s"
begin="infinityFill.begin"
values="1;1;0" keyTimes="0;.75;1" />
</use>
</svg>
</body>
正如标题所说,我正在尝试为一个 svg 制作动画,使其跨越看起来像省略号的路径以匹配它 第一个图标(无限符号)。我试过使用变换,但运动很糟糕。我怎样才能顺利(尽可能地)实现这一目标?
我找到了一些关于类似内容的文章,但它并没有让我得到我想要的平滑省略号: https://www.useragentman.com/blog/2013/03/03/animating-circular-paths-using-css3-transitions/ http://thenewcode.com/860/Animating-Elements-In-Arcs-Circles-and-Ellipses-With-CSS https://usefulangle.com/post/32/moving-an-element-in-circular-path-with-css
这里是link来一支笔https://codepen.io/acubaniti/pen/abNqzEV?editors=1100
代码如下:
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
animation: phone-orbits-cw 6s cubic-bezier(0.5, 0, 0.38, 0.1) infinite;
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
will-change: transform, opacity;
transform-style: preserve-3d;
transform-origin: center top;
}
#left, #right {
fill: none;
stroke-width: 10;
stroke-miterlimit: 10;
stroke: white;
transform-origin: 200px 150px;
animation-fill-mode: forwards;
stroke-dasharray: 314;
stroke-dashoffset: 314;
opacity: 0;
}
#right {
animation: circle-fill 6s linear infinite;
stroke: white;
transform: rotate(-180deg);
}
#left {
animation: circle-fill-left 6s linear infinite;
stroke: white;
transform: rotateX(180deg);
}
@keyframes circle-fill {
0%, 12.5% {
stroke-dashoffset: 314;
opacity: 1;
}
12.5%, 100% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
}
}
@keyframes circle-fill-left {
12.5% {
stroke-dashoffset: 314;
opacity: 1;
}
25% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
stroke-dashoffset: 0;
}
}
@keyframes phone-orbits-cw {
0% {
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
}
45% {
opacity: 0;
transform: rotate3d(0, 0, 1, -360deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 360deg);
}
58.75% {
opacity: 1;
transform: rotate3d(0, 0, 1, -360deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 360deg);
}
72.5% {
opacity: 1;
}
86.25% {
opacity: 1;
transform: rotate3d(0, 0, 1, 360deg) translate3d(-2%, -5%, 0) rotate3d(0, 0, 1, -360deg);
}
100% {
opacity: 1;
transform: rotate3d(0, 0, 1, -360deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, 360deg);
}
}
@keyframes phone-orbits-ccw {
0% {
opacity: 0;
transform: rotate3d(0, 0, 0) translate3d(0, 0, 0);
}
40% {
opacity: 0;
transform: rotate3d(0, 0, 1, 180deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, -180deg);
}
45% {
opacity: 1;
}
50% {
opacity: 1;
transform: rotate3d(0, 0, 1, -180deg) translate3d(2%, -5%, 0) rotate3d(0, 0, 1, 180deg);
}
75% {
opacity: 1;
}
100% {
opacity: 1;
transform: rotate3d(0, 0, 1, 180deg) translate3d(0, 0, 0) rotate3d(0, 0, 1, -180deg);
}
}
<html>
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300" xml:space="preserve">
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
</g>
<circle id="right" cx="200" cy="150" r="50"/>
<circle id="left" cx="100" cy="150" r="50"/>
</svg>
</body>
</html>
谢谢。
如果您可以在浏览器兼容性方面做出妥协,您应该能够使用 motion path 以更加语义化的方式做到这一点。它需要 Chrome 64、Edge 79、Firefox 72、Opera 45,但不支持 Safari。
以下示例以多种方式简化了您的代码。首先,我将这两个圆圈重写为一条路径,并另外移动它,使路径的起点和终点(“无穷大”符号的 mid-point)位于坐标系的原点。这是因为这条路径将被重用为运动路径——而不是引用,不幸的是,如果你继续使用 CSS,那是不可能的,但你可以只 copy-and-paste 路径命令。
为了让 stroke-dashoffset
动画更轻松一些,路径也获得了属性(不是 CSS 属性!)pathLength="100"
。这基本上是一个任意值,意味着:对于所有 distance-along-a-path 计算, 就好像 100 是路径长度。
其次,我使用相同的路径命令定义了一个 offset-path
。 offset-rotate
设置为 0deg
以避免 phone 在移动时沿路径切线旋转。 offset-distance
在动画期间从 0% 增加到 100%。
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
animation: phone-orbit 6s ease-in-out infinite;
opacity: 0;
offset-path: path("M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0");
offset-rotate: 0deg;
offset-distance: 0;
will-change: transform, opacity;
}
#infinity {
fill: none;
stroke-width: 6.666;
stroke: white;
animation-fill-mode: forwards;
stroke-dasharray: 100;
stroke-dashoffset: 100;
opacity: 0;
animation: infinity-fill 6s linear infinite;
}
@keyframes infinity-fill {
0%, 25% {
stroke-dashoffset: 100;
opacity: 1;
}
25%, 100% {
stroke-dashoffset: 0;
}
37.5% {
opacity: 1;
}
49%, 100% {
opacity: 0;
}
}
@keyframes phone-orbit {
0% {
opacity: 0;
}
40% {
opacity: 0;
}
45% {
opacity: 1;
}
50% {
offset-distance: 0%;
}
100% {
opacity: 1;
offset-distance: 100%;
}
}
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300" xml:space="preserve">
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
</g>
<path id="infinity" transform="translate(150 150) scale(1.666)" d="M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0" pathLength="100"/>
</svg>
</body>
为了获得更好的支持,您可以切换到 in-markup SMIL animation。只有旧的 IE 版本会让你失望。
主要区别在于不同动画的启动方式:除了第一个 infinityFill
动画在 0 秒启动外,每个后续动画的启动都与其运行时间有关。特别是 phoneOrbit
动画在 infinityFill
动画结束时开始,然后 phoneOrbit
结束再次开始 infinityFill
,以此类推。
此外,运动路径可以真正重复使用。
#gesture {
width: 300px;
height: 300px;
}
#phone {
fill: white;
opacity: 0;
will-change: transform, opacity;
}
#infinity {
fill: none;
stroke-width: 10;
stroke: white;
stroke-dasharray: 100;
opacity: 0;
}
<body style="background-color: black;">
<svg id="gesture" viewBox="0 0 300 300">
<defs>
<path id="motion" d="M0 0 A 30 30 0 0 1 60 0 A 30 30 0 0 1 0 0 A 30 30 0 0 0 -60 0 A 30 30 0 0 0 0 0" pathLength="100"/>
</defs>
<g id="phone">
<path d="M212.1 23.1H90.2c-4.8 0-8.8 3.9-8.8 8.8v236.4c0 4.8 3.9 8.8 8.8 8.8h121.9c4.8 0 8.8-3.9 8.8-8.8V31.8c0-4.8-4-8.7-8.8-8.7zm0 8.5c.1 0 .2.1.2.2v28.4H90.2V31.6h121.9zM90 268.2l.2-200.6h122.1l-.2 200.8-122.1-.2z"/>
<circle cx="151.1" cy="248.2" r="8.8"/>
<path d="M142 49.5h18.3c2.3 0 4.1-1.8 4.1-4.1s-1.8-4.1-4.1-4.1H142c-2.3 0-4.1 1.8-4.1 4.1s1.8 4.1 4.1 4.1z"/>
<animateMotion id="phoneOrbit" dur="3s"
begin="infinityFill.end" rotate="0">
<mpath href="#motion" />
</animateMotion>
<animate attributeName="opacity" dur="6s"
begin="infinityFill.begin"
values="0;0;1;1" keyTimes="0;.45;.5;1" />
</g>
<use id="infinity" href="#motion" transform="translate(150 150) scale(1.666)">
<animate id="infinityFill" attributeName="stroke-dashoffset"
dur="3s" begin="0s;phoneOrbit.end"
values="100;0;0" keyTimes="0;.5;1" />
<animate attributeName="opacity" dur="3s"
begin="infinityFill.begin"
values="1;1;0" keyTimes="0;.75;1" />
</use>
</svg>
</body>