【问题标题】:Why does SVG produce blurry image in Firefox for large ViewBox dimensions为什么 SVG 会在 Firefox 中为大 ViewBox 尺寸产生模糊图像
【发布时间】:2025-12-01 07:50:02
【问题描述】:

我正在尝试使用单个重复图像创建无限缩放效果。为了在不同尺寸范围内保持图像质量,我选择 使用 SVG。

问题是在 Firefox 中静止时图像很清晰,但是当缩放间隔(毫秒)设置为 100 毫秒或更短时,图像变得非常模糊。尤其是在缩放的初始阶段。

我已经为“形状渲染”样式属性尝试了不同的值,并在图形上设置了 preserveAspectRatio=false。 图像在 Chrome 中保持清晰。我不知道如何调试。

非常感谢您提供的任何帮助或想法!

示例:https://codepen.io/mannadu/pen/MMybwL

过滤器

 <?xml version="1.0"?>
                <svg xmlns="http://www.w3.org/2000/svg" id="yinyang" width="466" height="466" viewBox="-40 -40 80 80" preserveAspectRatio="false">
                        <g id="yyyy" transform="scale(20) translate(0 21.8)">
                        <g id="yyy">
                        <g id="yy">
                        <g id="y">
                        <circle id="yinner" r="39"/>
                        <path d="M0,38a38,38 0 0 1 0,-76a19,19 0 0 1 0,38a19,19 0 0 0 0,38" fill="#fff"/>
                        <circle cy="-19" r="5"/>
                        <circle cy="19" r="5" fill="#fff"/>
                        </g>
                        <use href="#y" transform="translate(0 , -19) scale(.15)"/>
                        <use href="#y" class="gany" transform="translate(0 , 19) scale(.15)"/>
                        </g>
                        <use href="#yy" transform="translate(0 , -19) scale(.15)" />
                        <use href="#yy" class="gany" transform="translate(0 , 19) scale(.15)" />
                        </g>
                        <use href="#yyy" transform="translate(0 , -19) scale(.15)" />
                        <use href="#yyy" class="gany" transform="translate(0 , 19) scale(.15)" />
                        </g>
                </svg>

【问题讨论】:

  • 您不应该对viewBox 属性进行补间。相反,请尝试将 SVG 的所有内部内容包装在另一个 &lt;g&gt; 元素中,并将其动画化为 transform 属性。
  • 我用于 preserveAspectRatio 的值应该是“none”而不是“false”,这似乎很有帮助。虽然还是有一些瞬间的模糊

标签: html firefox svg


【解决方案1】:

从我的评论中进一步阐述:你不应该改变/更新viewBox 属性,因为浏览器必须不断地重新计算 SVG 元素的内部比例。这可能会导致性能问题或抗锯齿问题,例如您在 Firefox 中看到的问题。

您要做的是简单地对您的 SVG 元素应用另一个变换。最快的、创可贴的修复方法是简单地将 SVG 元素的内部 HTML 包装在 &lt;g&gt; 元素中:

<svg xmlns="http://www.w3.org/2000/svg" id="yinyang" width="466" height="466" viewBox="-40 -40 80 80">
  <g id="outer" transform="scale(1)">
    <!-- Original SVG content -->
  </g>
</svg>

然后,使用 JS,您可以在间隔回调中更改其 transform 属性:

const yinyang = document.getElementById("yinyang");
const outer = document.getElementById('outer');
const scalefactor = 1.01;

let scale = 1;
const zoomyInterval = setInterval(() => {
    scale /= scalefactor;
    outer.setAttribute('transform', `scale(${scale})`);
  },
  100
);

在此处查看概念验证(另见 forked your CodePen):

const yinyang = document.getElementById("yinyang");
const outer = document.getElementById('outer');
const scalefactor = 1.01;

let scale = 1;
const zoomyInterval = setInterval(() => {
    scale /= scalefactor;
    outer.setAttribute('transform', `scale(${scale})`);
  },
  100
);
.gany {
  filter: invert(1);
  -webkit-filter: invert(1);
  /* Not working for svg <use> elements in Chrome */
}

svg {
  shape-rendering: geometricPresicion;
  /* 
  Attempt to address blurry rasterized image in Firfox */
}

circle {
  position: relative;
}
<svg xmlns="http://www.w3.org/2000/svg" id="yinyang" width="466" height="466" viewBox="-40 -40 80 80">
  <g id="outer" transform="scale(1)">
    <g id="yyyy" transform="scale(20) translate(0 21.8)">
      <g id="yyy">
        <g id="yy">
          <g id="y">
            <circle id="yinner" r="39" />
            <path d="M0,38a38,38 0 0 1 0,-76a19,19 0 0 1 0,38a19,19 0 0 0 0,38" fill="#fff" />
            <circle cy="-19" r="5" />
            <circle cy="19" r="5" fill="#fff" />
          </g>
          <use href="#y" transform="translate(0 , -19) scale(.15)" />
          <use href="#y" class="gany" transform="translate(0 , 19) scale(.15)" />
        </g>
        <use href="#yy" transform="translate(0 , -19) scale(.15)" />
        <use href="#yy" class="gany" transform="translate(0 , 19) scale(.15)" />
      </g>
      <use href="#yyy" transform="translate(0 , -19) scale(.15)" />
      <use href="#yyy" class="gany" transform="translate(0 , 19) scale(.15)" />
    </g>
  </g>
</svg>

更好:使用window.requstAnimationFrame()

您会注意到,即使上面的示例有效,您的动画仍然不稳定:这是因为您仅以 100 毫秒的间隔更新 &lt;g&gt; 元素的比例,这转换为 10fps 的帧速率。这种运动看起来不会流畅。你想要的是在浏览器重绘时平滑计算下一个scale

如果我们稍微重构您的大小调整逻辑,您就会得到这样的结果:

const yinyang = document.getElementById("yinyang");
const outer = document.getElementById('outer');
const scalefactor = 1.01;
let scale = 1;

let start = null;
const zoomStep = (timestamp) => {
  if (!start) {
    start = timestamp;
  }
  
  const progress = timestamp - start;
  
  // Here, we want to shrink the scale by the scalefactor by exponential transformation
  // You can change the `500` value to whatever value you want to achieve the speed you desire
  scale = scale / Math.pow(scalefactor, progress / 500);
  outer.setAttribute('transform', `scale(${scale})`);
  
  // Optional: Arbirary limit to stop animation
  if (scale > 0.01) {
    window.requestAnimationFrame(zoomStep);
  }
};

window.requestAnimationFrame(zoomStep);

查看改进的概念验证:

const yinyang = document.getElementById("yinyang");
const outer = document.getElementById('outer');
const scalefactor = 1.01;
let scale = 1;

let start = null;
const zoomStep = (timestamp) => {
  if (!start) {
    start = timestamp;
  }
  
  const progress = timestamp - start;
  
  // Here, we want to shrink the scale by the scalefactor by exponential transformation
  // You can change the `500` value to whatever value you want to achieve the speed you desire
  scale = scale / Math.pow(scalefactor, progress / 500);
  outer.setAttribute('transform', `scale(${scale})`);
  
  // Optional: Arbirary limit to stop animation
  if (scale > 0.01) {
    window.requestAnimationFrame(zoomStep);
  }
};

window.requestAnimationFrame(zoomStep);
.gany {
  filter: invert(1);
  -webkit-filter: invert(1);
  /* Not working for svg <use> elements in Chrome */
}

svg {
  shape-rendering: geometricPresicion;
  /* 
  Attempt to address blurry rasterized image in Firfox */
}

circle {
  position: relative;
}
<svg xmlns="http://www.w3.org/2000/svg" id="yinyang" width="466" height="466" viewBox="-40 -40 80 80">
  <g id="outer" transform="scale(1)">
    <g id="yyyy" transform="scale(20) translate(0 21.8)">
      <g id="yyy">
        <g id="yy">
          <g id="y">
            <circle id="yinner" r="39" />
            <path d="M0,38a38,38 0 0 1 0,-76a19,19 0 0 1 0,38a19,19 0 0 0 0,38" fill="#fff" />
            <circle cy="-19" r="5" />
            <circle cy="19" r="5" fill="#fff" />
          </g>
          <use href="#y" transform="translate(0 , -19) scale(.15)" />
          <use href="#y" class="gany" transform="translate(0 , 19) scale(.15)" />
        </g>
        <use href="#yy" transform="translate(0 , -19) scale(.15)" />
        <use href="#yy" class="gany" transform="translate(0 , 19) scale(.15)" />
      </g>
      <use href="#yyy" transform="translate(0 , -19) scale(.15)" />
      <use href="#yyy" class="gany" transform="translate(0 , 19) scale(.15)" />
    </g>
  </g>
</svg>

【讨论】:

  • 我也考虑过调整图像变换,但没有意识到它会提高多少性能。重新设计的变换后,我仍然在 FF 中感到有些模糊,但无论如何感谢您的帮助!
  • @mannadu 不客气!我还用更现代的解决方案更新了我的答案,它使用window.requestAnimationFrame 而不是setInterval:如果您查看改进的sn-p,动画会更流畅:)
  • 在你的第一句话中(“你应该改变/更新 viewBox 属性”)——你不是说“不应该”吗?
最近更新 更多