在动画将圆圈移动到正确位置之前,SVG圆圈会短暂地出现在 (0,0) 位置——如何修复?
我有一个用于加载动画的会闪烁的圆圈的SVG。圆圈通过 cx 和 cy 定位在固定坐标上:
<circle cx="316" cy="59.5" r="7" fill="url(#dg2)" filter="url(#glow)">
<animate
attributeName="r"
dur="1.5s"
repeatCount="indefinite"
values="7;11;7"
keyTimes="0;0.5;1"
calcMode="spline"
keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"
/>
</circle>
在硬重新加载时(浏览器无关,在Firefox与 Chrome上测试过),圆圈会在正确位置出现之前短暂地闪现到一个错误的位置——明显位于页面的左上角区域,然后才出现在正确的位置。
我尝试了以下方法但都不成功:
- 先将
opacity="0"设为初始状态并让它进入动画 - 在容器上使用
visibility: hidden,然后在Angular之后通过AfterViewInit切换 - 将SVG包裹在一个容器中,并用
requestAnimationFrame或setTimeout延迟渲染 - 在SVG元素上使用
overflow: hidden - 将圆圈放在一个
<g>内,并使用CSS的visibility动画
该组件通过Angular的 @if 指令按条件渲染(当前使用的是Angular v20)。该问题只在硬重新加载时出现,随后导航时不会。
en
解决方案
为防止初始的未样式化/未定位内容闪现,请使用:
- https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/visibility
- 用
visibility="hidden"隐藏圆圈 - 用
<set attributeName="visibility" from="hidden" to="visible" begin="1s" />显示圆圈 - 如果你希望时序与动画匹配,在
animate上添加begin属性 - 更改
begin的取值 - 为了实现更平滑的动画,
opacity的开始也要进行动画
<svg viewBox="0 0 30 30" style="background:pink;width:160px">
<circle cx="15" cy="15" r="7" fill="green" visibility="hidden">
<set attributeName="visibility" from="hidden" to="visible" begin="1s" />
<animate attributeName="r" dur="1.5s" repeatCount="indefinite"
begin="1s"
values="7;11;7" keyTimes="0;0.5;1"
calcMode="spline" keySplines="0.4 0 0.6 1; 0.4 0 0.6 1" />
<animate attributeName="opacity" from="0" to="1" fill="freeze"
begin="1s" dur="1.5s"/>
</circle>
</svg>
需要注意的事项,避免日后头疼:
- 与HTML不同,SVG即使父元素被设为隐藏,子元素仍有可能可见;在子元素上设置
visiblity="inherit"(MDN未对此有文档说明)
<g visibility="hidden">
<circle visibility="inherit" ... />
</g>
备选方案
TL;DR: 闪烁并非我最初提问时关于脉冲点所致——这是由同一SVG中的其他圆圈使用 <animateMotion>,但没有明确的 cx/cy 值所致。修复需要 cx="0" cy="0" 加上 visibility="hidden",并带有延迟的 <animate fill="freeze"> 使它们可见。
我的失败在于找错了地方。起初我聚焦在位于 cx="316" cy="59.5" 的一个脉动圆圈。但实际的SVG包含 另外两个圆圈,它们使用 <animateMotion> 在运动路径上移动。这些圆圈没有设置 cy 属性,默认值为 0。因此在硬重新加载时——在SMIL生效之前——它们在 (cx, 0) 处短暂呈现,视觉上相当于SVG的顶部边缘,视布局而定也可能在浏览器视口的左上角。
脉动圆圈只是一个误导。仅隐藏它并无帮助,因为闪现来自于那些在运动路径上的圆圈。
有两个相互作用的问题:
animateMotion圆圈的默认位置
<animateMotion> 通过相对于元素原点应用平移变换来工作。要使运动路径在绝对坐标处呈现,圆圈应该位于 cx="0" cy="0"。但这意味着若不进行干预,圆圈在SMIL启动前会初始在SVG原点(左上角)可见。
<set>不会在repeatCount="indefinite"时持续
使用 <set attributeName="visibility" to="visible" begin="0.1s" /> 起初有效,但在某些浏览器中,同一元素上重复动画之间的可见性状态会被重置,导致圆点在循环中间消失再出现。
为解决这个问题,我尝试过但未成功的方法:
- 在圆圈上应用
opacity="0"(滤镜会创建独立的复合层,可能渗透/泄漏) - 在CSS中对父容器应用
visibility: hidden(SVG子元素不会从父级继承visibility,子元素需要显式的visibility="inherit"或自己的隐藏状态) - 在包裹的div上使用
opacity: 0并伴随淡入(某些浏览器在初始绘制阶段,滤镜会绕过父元素的不透明度) - 通过Angular的
AfterViewInit/requestAnimationFrame/setTimeout切换可见性(太晚了——闪现发生在Angular的生命周期钩子触发之前) - 使用
<set>的可见性元素(在重复时会丢失)
最终奏效的方法:
对于每个 animateMotion 圆圈:设置 cx="0" cy="0"、添加 visibility="hidden",并使用 <animate ... fill="freeze"> 代替 <set>,以在短暂延迟后使其可见。延迟至少要大于浏览器完成初始布局所需的时间(100ms足够)。
<!-- Circle traveling along a motion path -->
<circle cx="0" cy="0" r="7" fill="url(#myGradient)" filter="url(#glow)"
opacity="0" visibility="hidden">
<animate attributeName="visibility" to="visible"
begin="0.1s" dur="1s" fill="freeze" />
<animateMotion
dur="2.2s"
begin="0.1s"
repeatCount="indefinite"
calcMode="spline"
keyPoints="0;1"
keyTimes="0;1"
keySplines="0.2 0 0.8 1">
<mpath href="#myPath" />
</animateMotion>
<animate attributeName="opacity"
dur="2.2s" begin="0.1s" repeatCount="indefinite"
values="0;1;1;0" keyTimes="0;0.1;0.85;1" />
</circle>
<!-- Stationary pulsating circle -->
<circle cx="316" cy="59.5" r="7" fill="url(#myGradient)" filter="url(#glow)"
visibility="hidden">
<animate attributeName="visibility" to="visible"
begin="0.1s" dur="1s" fill="freeze" />
<animate attributeName="r"
dur="1.5s" begin="0.1s" repeatCount="indefinite"
values="7;11;7" keyTimes="0;0.5;1"
calcMode="spline" keySplines="0.4 0 0.6 1; 0.4 0 0.6 1" />
</circle>
速查表:
- 对于
<animateMotion>圆圈,cx="0" cy="0"是正确的原点——但你必须在初始就隐藏它们,否则它们会在SVG原点闪现 - 对于具有重复动画的元素,使用
<animate ... fill="freeze">替代<set>来控制可见性 - 在同一元素上将可见性、运动和不透明度动画的所有
begin值同步 - 当SVG在硬重新加载时闪烁,请检查每个带有动画的元素是否缺少初始坐标——不仅仅是看起来可疑的那个