简体   繁体   中英

Why does SVG produce blurry image in Firefox for large ViewBox dimensions

I'm trying to create an infinite zoom effect with a single repeating image.To preserve image quality across different size scales, I chose to use SVG.

The problem is that the image is crisp when stationary in Firefox, but it becomes very blurry when the zoom interval(ms) is set to anything 100ms or less. Especially at the initial stages of the zoom.

I've tried different values for "shape-rendering" style property as well as setting preserveAspectRatio=false on the graphic. The image stays crisp in Chrome. I don't know how to debug this.

I really appreciate any help or ideas you can offer!

Example: https://codepen.io/mannadu/pen/MMybwL

Filter

 <?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>

To further elaborate from my comment: you should be mutating/updating the viewBox attribute, because then the browser has to continuously recalculate the inner scale of the SVG element. This can result in performance issues or anti-aliasing issues, such as what you're seeing in Firefox.

What you're trying to do is to simply apply yet another transform to your SVG element. The fastest, band-aid kind of way to fix it is to simply wrap the inner HTML of your SVG element in a <g> element:

<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>

Then, using JS, you can change its transform attribute in your interval callback:

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
);

See proof-of-concept here (also 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> 

Even better: use window.requstAnimationFrame()

You will notice that even though the above example works, your animation remains choppy: that is because you are only updating the scale of the <g> element at 100ms intervals, which translates to a frame rate of 10fps. That movement is not going to look fluid. What you want, is to smoothly calculate the next scale whenever the browser is repainting.

If we lightly refactor your resizing logic, you have something like this:

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);

See improved proof-of-concept:

 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM