1

I have a div with an SVG background. The div height and width do not change. The only thing that changes is the SVG file used for the background when the div is hovered.

The issue seems to be that when the SVG background is replaced (even after the asset is preloaded) the image seems to "shake" before settling into place. I've triple checked and both SVG assets are the exact same height and width and viewport, the only difference is the coloring.

For reference, the issue can be seen on this page: https://tqt.uwaterloo.ca/.

When hovering the "hamburger" icon the issue can be seen. When hovering the "search" icon there is no issue, however the "search" icon has the exact same SVG replacement on it and it's working as expected.

Final note: This issue is only replicatable on retina (2x+) monitors.

KoldBane
  • 207
  • 2
  • 13
  • One easy solution to your problem is eliminating the transition. If you need to keep the transition you can use inline svg and transition the fill of the path instead of changing the image – enxaneta Jun 27 '20 at 07:13
  • 1
    @enxaneta I've removed the transition CSS attribute and saw no difference. The suggestion of using inline SVG and transitioning the fill is a good idea. If I can't get a working solution by replacing the background-image, I'll explore that! – KoldBane Jun 27 '20 at 18:57

1 Answers1

3

There are two bugs here, one in Blink (Chrome and Chromium based browsers) and one in webkit, both caused by the transitioning of the background-image property.

Here is a minimal repro of these bugs:

const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 26">
<path d="M0,0h34v4H0V0z M0,22h34v3.9H0V22z M0,11h34v4H0V11z" fill="black"/>
</svg>`;
const url = 'url(data:image/svg+xml,' + encodeURIComponent( svg ) + ')';
const urls = [ url, url.replace( 'black', 'red' ) ];
let i = 0;
setInterval(() =>
  document.querySelector( '.burger' ).style.backgroundImage = urls[ (++i % 2) ],
  500
);
.burger {
  height: 30px;
  width: 100px;
  background-position: center;
  background-repeat: no-repeat;
  transition: background-image .2s linear;
}
<div class="burger">
</div>

Chrome's bug is due to the fact your svg files don't have their own width and height attributes, setting one will fix it there.

Safari bug is caused by the retina scaling, on which they weirdly apply the transition too... For this one a fix would be harder, but setting the correct height will make it less visible since we'd only have antialiasing artifacts to move.

Here is the same snippet as above, with just the height and width attributes set:

const svg = `<svg width="34" height="26" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 26">
<path d="M0,0h34v4H0V0z M0,22h34v3.9H0V22z M0,11h34v4H0V11z" fill="black"/>
</svg>`;
const url = 'url(data:image/svg+xml,' + encodeURIComponent( svg ) + ')';
const urls = [ url, url.replace( 'black', 'red' ) ];
let i = 0;
setInterval(() =>
  document.querySelector( '.burger' ).style.backgroundImage = urls[ (++i % 2) ],
  500
);
.burger {
  height: 30px;
  width: 100px;
  background-position: center;
  background-repeat: no-repeat;
  transition: background-image .2s linear;
}
<div class="burger">
</div>

Now, there should probably be a note that if targeting only quite recent browsers is not an issue for you, you could achieve the same effect by using only CSS filters, saving one network request:

const svg = `<svg width="34" height="26" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 26">
<path d="M0,0h34v4H0V0z M0,22h34v3.9H0V22z M0,11h34v4H0V11z"/>
</svg>`;
// This time we generate only a single black url
const url = 'url(data:image/svg+xml,' + encodeURIComponent( svg ) + ')';
document.querySelector( '.burger' ).style.backgroundImage = url;
.burger {
  height: 30px;
  width: 100px;
  background-position: center;
  background-repeat: no-repeat;
  transition: filter .2s linear;
}
.burger:hover {
  /*
    Using formula from 
    https://stackoverflow.com/a/43959856/3702797
    to safely fallback on black for browsers withtou support for filters
  */
  filter: invert(10%) sepia(100%) saturate(10000%) ;
}
hover the icon to change its color.
<div class="burger">
</div>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    This is great, very thorough and multiple options. I went with the last option as IE11 support wasn't a big deal. Thanks again! – KoldBane Jun 29 '20 at 03:07