Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cache SVG icons on an external CDN while avoiding FOMI?

I know how to get SVG icons loading on my website, but what I can't figure out is how to satisfy all the following constraints:

  1. Ability to use SVG icons in CSS
  2. No flash of missing icons (FOMI)
  3. Minimal initial page size
  4. Cached SVGs
  5. Ability to use a CDN
  6. Must be able to use fill: currentColor to make the icon match the current text color, just like icon-fonts
  7. Bonus: Pixel-align the SVGs so they always look sharp

1,2,3 and 4 can be satisfied by using an external sprite map like so:

<svg viewBox="0 0 100 100">
    <use xmlns:xlink="http://www.w3.org/1999/xlink"
         xlink:href="/assets/sprite-4faa5ef477.svg#icon-asterisk-50af6"></use>
</svg>

But we can't use a CDN until browsers fix the CORS issue.

We can patch in support for external domains, but I'm pretty sure this won't work for CSS because it only watches the DOM (sorry, haven't tested yet), and also it causes your browser to make a whole bunch of failed requests to a file it can't fetch (one for each icon on the page).

We can use a CDN if instead we either inline the entire SVG (increased page size, no caching) or we AJAX it in (causes FOMI).

So, are there any solutions that satisfy all 5 7 constraints?

Basically I want SVGs to be just as convenient as icon-fonts or there's no point switching over. SVGs support multiple colors and are more accessible but I can't get them to look as good, or load as efficiently.

like image 705
mpen Avatar asked Feb 10 '17 21:02

mpen


2 Answers

I had pretty much the same problem. This probably doesn't satisfy the FOMI requirement, but it's an interesting hack that got me out of a bind. Basically, this script just swaps every img in the DOM that imports an svg with inline SVG, so you can style it how you want.

// replaces img tags with svg tags if their source is an svg
// allows SVGs to be manipulated in the DOM directly
// 💡 returns a Promise, so you can execute tasks AFTER fetching SVGs

let fetchSVGs = () => {

//gets all the SRCs of the SVGs
let parentImgs = Array.from(document.querySelectorAll('img')).map((img) => {
    if(img.src.endsWith('.svg')) {
        return img
    }
});

let promises = [];
parentImgs.forEach((img) => {
    promises.push(
        fetch(img.src).then((response) => {
            // Error handling
            if (response.status !== 200) {
                console.log('Looks like there was a problem. Status Code: ' +
                    response.status);
                return;
            }
            // saves the SVG
            return response.text();
        })
    )
});

// All fetch() calls have been made
return Promise
    .all(promises)
    .then((texts)=> {
        texts.forEach((text, i) => {
            let img = parentImgs[i];

            let div = document.createElement('div');
            div.innerHTML = text;
            img.parentNode.appendChild(div);
            let svg = div.firstChild;
            img.parentNode.appendChild(svg);

            // makes the SVG inherit the class from its parent
            svg.classList = img.className;

            // removes the junk we don't need.
            div.remove();
            img.parentNode.removeChild(img);

        })
    })
    .catch((error) => {
        console.log(error);
    })
};

Otherwise, I came across this on Twitter today https://twitter.com/chriscoyier/status/1124064712067624960 and applying this CSS to a div allowed me to make a colourable svg icon that can be stored in a CDN

.icon-mask {
  display: inline-block;
  width: 80px;
  height: 80px;
  background: red;
   -webkit-mask: url(https://cdnjs.cloudflare.com/ajax/libs/simple-icons/3.0.1/codepen.svg);
   -webkit-mask-size: cover;
}

Browser support isn't perfect yet though.

Hope this helps someone 😄

like image 176
JCLaHoot Avatar answered Sep 29 '22 03:09

JCLaHoot


The closest I could get is loading an SVG in an image element and then using it like an "old-fashioned" image sprite. This, as far as I can tell, satisfies all of your constraints. The only disadvantage I can think of is that you lose the ability to modify specific parts of the SVG using CSS. This is however not one of your constraints (correct me if I'm wrong) and it is still possible to modify all of the icon, as you can see in my demo. I created a fiddle and for completeness also include a code snippet.

To emulate a CDN, I created an SVG file and uploaded it to some image hosting service. My apologies to future readers if this service is now down. The SVG file simply has all icons next to each other in it (I created a black square, circle and triangle for now). The difference with SVG sprite maps is thus that the icons are in the SVG itself, not in the defs. It should be quite easy to combine multiple SVGs in a single one, I have not looked for tools that would automate this process.

.icon {
  display: inline-block;
  vertical-align: top;
  width: 30px; /* can be anything */
  height: 30px;
  background-image: url('http://imgh.us/icons_36.svg');
  
  border: 1px solid #000; /* just to see where the icon is */
}

/* sizes */
.icon.large {
  width: 50px;
  height: 50px;
  background-size: 150px auto;
}

/* icons */
.icon.circle { background-position: -30px 0; }
.icon.large.circle { background-position: -50px 0; }
.icon.triangle { background-position: -60px 0; }
.icon.large.triangle { background-position: -100px 0; }

/* styles */
.icon.info {
  /* based on http://stackoverflow.com/a/25524145/962603,
   * but you can of course also use an SVG filter (heh) */
  filter: invert(100%) sepia(100%) saturate(50000%) hue-rotate(90deg) brightness(70%);
}
.icon.highlight {
  /* based on http://stackoverflow.com/a/25524145/962603,
   * but you can of course also use an SVG filter (heh) */
  filter: invert(100%) sepia(100%) saturate(10000%) hue-rotate(30deg) brightness(50%);
}
<span class="icon square"></span>
<span class="icon circle"></span>
<span class="icon triangle"></span>
<span class="icon circle highlight"></span>
<span class="icon triangle large info"></span>
like image 35
Just a student Avatar answered Sep 29 '22 03:09

Just a student