如何在外部CDN上缓存SVG图标,同时避免FOMI?

13

我知道如何在我的网站上加载SVG图标,但我无法弄清以下所有约束条件的满足方法:

  1. 能够在CSS中使用SVG图标
  2. 没有缺失图标的闪烁现象 (FOMI)
  3. 初始页面尺寸最小化
  4. 缓存SVGs
  5. 能够使用CDN
  6. 必须能够使用fill: currentColor来使图标与当前文本颜色匹配,就像图标字体一样
  7. 奖励: 对SVG进行像素对齐以使它们始终清晰锐利

通过使用外部雪碧图,可以满足1、2、3和4:

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

但在浏览器修复CORS问题之前,我们无法使用CDN。

我们可以添加对外部域的支持,但我很确定这对于CSS不起作用,因为它只监视DOM(抱歉,还没有测试),而且它会导致您的浏览器制作大量无法获取文件的失败请求(每个页面上的图标都是一个请求)。

如果我们内联整个SVG(增加页面大小,无缓存),或者使用AJAX加载它(导致FOMI),那么我们可以使用CDN。

那么,有没有满足所有57个约束条件的解决方案呢?

基本上,我希望SVG图像与图标字体一样方便,否则就没有切换的意义。 SVG支持多种颜色,并且更易于访问,但我无法使它们看起来好看,并且加载效率也不高。


你可以将你的SVG转换成图标字体 http://fontello.com/ - Holger Will
@HolgerWill 是的。我目前正在使用icomoon的图标字体,但是每个人都说SVG是当今的潮流,我正在努力弄清楚它们究竟更好在哪里。它们支持多种颜色,当然,但是它们看起来不够清晰,并且它们不能与CDN一起使用,所以现在SVG看起来有点糟糕。由于FontCustom似乎已经死了,所以使用webpack构建它们会稍微容易一些。 - mpen
1
我猜大概是类似 Polymer 所做的事情... 将您的图标放在一个 HTML 文件中,并使用 HTML 包含与自定义元素相结合的方式。 - Holger Will
4个回答

4
我能做的最接近的方法是在图像元素中加载SVG,然后像“老式”image sprite一样使用它。据我所知,这满足了你所有的限制条件。我能想到的唯一缺点是你失去了使用CSS修改SVG特定部分的能力。然而,这不是你的限制之一(如果我错了,请纠正我),并且仍然可以修改所有的图标,正如你在我的演示中所看到的那样。我创建了一个fiddle,为了完整起见,还包括了代码片段。
为了模拟CDN,我创建了一个SVG文件并上传到某个图像托管服务。如果这个服务现在已经关闭,那么我向未来的读者道歉。SVG文件简单地将所有图标放在一起(我现在只创建了一个黑色正方形、圆形和三角形)。因此,与SVG精灵图不同的是,图标位于SVG本身中,而不是defs中。将多个SVG组合成一个应该很容易,我没有寻找自动化这个过程的工具。

.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 https://dev59.com/G2Qm5IYBdhLWcg3wxBRg#25524145,
   * 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 https://dev59.com/G2Qm5IYBdhLWcg3wxBRg#25524145,
   * 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>


啊哇,我本来以为我可以使用 fill: currentColor 等等的方法。这是一个秘密要求,但我没有列出来,所以我会给你赏金,但我会更新问题。有创意的想法! - mpen
太棒了!非常感谢你。我会尝试想出一个解决方案,让你能够做到那一点。我猜一种方法是按照我的建议去做,然后使用 AJAX 加载所有内容,并在加载时替换图标?不过这样会很麻烦... - Just a student
我现在正在做类似的事情,但我不喜欢它,因为 AJAX 请求完成后图标会改变颜色。我需要进行更多的实验;也许如果我立即在异步脚本中启动一个 AJAX 请求来立即加载 spritemap,那么 FOMI 就不会那么糟糕了。 - mpen

1

数据URI不可缓存。在CSS中使用可能没问题,但我不想在我的初始页面加载HTML中重复200次1000字节的数据URI。 - mpen
1
好的,希望你能想出个办法。 :) - Serg Chernata

1
我遇到了几乎相同的问题。这可能不符合FOMI的要求,但这是一个有趣的技巧,让我摆脱了困境。基本上,这个脚本只是将DOM中导入SVG的每个img与内联SVG交换,因此您可以按照自己的喜好进行样式设置。
// 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);
    })
};

否则,我今天在Twitter上看到了这个https://twitter.com/chriscoyier/status/1124064712067624960,将此CSS应用于一个div,使我可以制作可着色的svg图标,并将其存储在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;
}

浏览器支持还不完美。

希望这能帮助到某些人。


0

已被弃用:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Using_the_application_cache - mpen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接