内联 SVG… 缓存

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费信用额度!

我写过使用内联 <svg> 图标可以实现 最佳图标系统。我认为这仍然是正确的。这是将图标添加到页面上的最简单方法。没有网络请求,可以完美地进行样式设置。

但是内联代码有一些缺点,其中之一是它没有利用缓存。当您在页面之间浏览时,浏览器会一遍又一遍地读取和处理相同的代码。这并不是什么大问题。还有更多需要关注的性能问题,对吧?但考虑更有效的模式仍然很有趣。

Scott Jehl 写道 即使您内联某些内容,也不意味着您不能将其缓存。让我们看看 Scott 的想法是否可以扩展到 SVG 图标。

从内联 SVG 开始

像这样…

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Inline SVG</title>
  <link rel="stylesheet" href="/styles/style.css">
</head>

<body>

  ...
 
  <svg width="24" height="24" viewBox="0 0 24 24" class="icon icon-alarm" xmlns="http://www.w3.org/2000/svg">
    <path id="icon-alarm" d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z"></path>
  </svg>

将文本作为文件放入浏览器缓存非常容易

在上面的 HTML 中,选择器 .icon-alarm 将为我们获取该图标的整个 SVG 块。

const iconHTML = document.querySelector(".icon-alarm").outerHTML;

然后,我们可以将其放入浏览器的缓存中,如下所示

if ("caches" in window) {
  caches.open('static').then(function(cache) {
    cache.put("/icons/icon-wheelchair.svg", new Response(
      iconHTML,
      { headers: {'Content-Type': 'image/svg+xml'} }
    ));
  }
}

看到文件路径 /icons/icon-wheelchair.svg 了吗?这个路径是编造的。但它确实会放在该位置的缓存中。

让我们确保浏览器在请求该文件时将其从缓存中获取

我们将在页面上注册一个服务工作者

if (navigator.serviceWorker) {   
  navigator.serviceWorker.register('/sw.js', {
    scope: '/'
  });
}

服务工作者本身非常小,只是一个缓存匹配器

self.addEventListener("fetch", event => {
  let request = event.request;

  event.respondWith(
    caches.match(request).then(response => {
      return response || fetch(request);
    })
  );
});

但是… 我们从未请求过该文件,因为我们的图标是内联的。

是的。但是,如果其他页面也从该缓存中受益呢?例如,可以像这样在页面上放置一个 SVG 图标

<svg class="icon">
  <use xlink:href="/icons/icon-alarm.svg#icon-alarm" /> 
</svg>

由于 /icons/icon-alarm.svg 已经准备好放在缓存中,浏览器会很乐意将其从缓存中取出并显示。

(我对这个方法有效感到有点惊讶。Edge 不喜欢链接到文件的 <use> 元素,但这种情况很快就会结束。**更新**,它已经结束了,Edge 已经使用 Chromium 了。)

即使该文件不在缓存中,假设我们实际上将该文件放在文件系统上,可能是某种“包含”的结果(我在演示中使用了 Nunjucks)。

但是… <use> 和内联 SVG 并不完全相同

是的。我喜欢上面这种方法是因为它利用了缓存,并且图标应该几乎立即呈现。您还可以通过这种方式进行一些样式设置 - 例如,为父图标设置填充应该会通过 <use> 创建的 shadow DOM 传递,并对其中的 SVG 元素进行着色。

不过,它并不完全相同。与内联 SVG 相比,shadow DOM 是一个很大的障碍。

因此,请对其进行增强!我们可以异步加载一个脚本,该脚本找到每个 SVG 图标,Ajax 请求它需要的 SVG,并替换 <use> 内容…

const icons = document.querySelectorAll("svg.icon");

icons.forEach(icon => {
  const url = icon.querySelector("use").getAttribute("xlink:href"); // Might wanna look for href also
  fetch(url)
    .then(response => response.text())
    .then(data => {
      // This is probably a bit layout-thrashy. Someone smarter than me could probably fix that up.

      // Replace the <svg><use></svg> with inline SVG
      const newEl = document.createElement("span");
      newEl.innerHTML = data;
      icon.parentNode.replaceChild(newEl, icon);

      // Remove the <span>s
      const parent = newEl.parentNode;
      while (newEl.firstChild) parent.insertBefore(newEl.firstChild, newEl);
      parent.removeChild(newEl);
    });
});

现在,假设这个 JavaScript 正确执行,这个页面就有了与原始页面相同的内联 SVG。

演示和代码库