如何用 CSS 自定义属性制作图标系统

Avatar of Louis Hoebregts
Louis Hoebregts

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

SVG 是网站图标的最佳格式,毫无疑问。它允许您无论屏幕像素密度如何都能拥有清晰的图标,您可以在悬停时更改 SVG 的样式,甚至可以使用 CSS 或 JavaScript 动画图标。

在网页中包含 SVG 的方法有很多,每种技术都有其自身的优缺点。在过去几年里,我一直使用 Sass 函数在我的 CSS 中直接导入我的图标,避免弄乱我的 HTML 标记。

我有一个包含所有图标源代码的 Sass 列表。然后,每个图标都会使用 Sass 函数编码为数据 URI 并存储在网页根目录的 自定义属性 中。

TL;DR

我在这里提供给您的是一个 Sass 函数,它可以直接在您的 CSS 中创建 SVG 图标库。

SVG 源代码使用 Sass 函数编译,该函数将它们编码为数据 URI,然后将图标存储在 CSS 自定义属性中。您可以在您的 CSS 中随处使用任何图标,就像它是一个外部图像一样。

这是从我的个人网站代码中提取的一个示例

.c-filters__summary h2:after {
  content: var(--svg-down-arrow);
  position: relative;
  top: 2px;
  margin-left: auto;
  animation: closeSummary .25s ease-out;
}

演示

Sass 结构

/* All the icons source codes */
$svg-icons: (
  burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0...'
);

/* Sass function to encode the icons */
@function svg($name) {
  @return url('data:image/svg+xml, #{$encodedSVG} ');
}

/* Store each icon into a custom property */
:root {
  @each $name, $code in $svg-icons {
    --svg-#{$name}: #{svg($name)};
  }
}

/* Append a burger icon in my button */
.menu::after {
  content: var(--svg-burger);
}		

这种技术有利有弊,因此请在将此解决方案实施到您的项目中之前仔细考虑它们。

优点

  • 没有针对 SVG 文件的 HTTP 请求。
  • 所有图标都存储在一个位置。
  • 如果需要更新图标,您无需查看每个 HTML 模板文件。
  • 图标会与您的 CSS 一起缓存。
  • 您可以手动编辑图标的源代码。
  • 它不会通过添加额外的标记来污染您的 HTML。
  • 您仍然可以使用 CSS 更改图标的颜色或某些方面。

缺点

  • 您无法使用 CSS 动画或更新 SVG 的特定部分。
  • 图标越多,编译后的 CSS 文件就越重。

我主要将此技术用于图标,而不是徽标或插图。编码的 SVG 始终会比其原始文件更重,因此我仍然使用外部文件(使用 <img> 标签或在 CSS 中使用 url(path/to/file.svg))加载复杂的 SVG。

将 SVG 编码为数据 URI

将您的 SVG 编码为数据 URI 并不新鲜。事实上,Chris Coyier 十年前就写了一篇文章介绍如何使用这种技术以及为什么要(或不应)使用它。

有两种方法可以在 CSS 中使用数据 URI 的 SVG

  • 作为外部图像(使用 background-image,border-image,list-style-image,…)
  • 作为伪元素的内容(例如 ::before::after

以下是一个基本示例,展示了如何使用这两种方法

此特定实现的主要问题是,您每次需要新图标时都必须手动转换 SVG,而且在 CSS 中拥有这种长长的不可读代码字符串并不令人愉快。

这就是 Sass 派上用场的地方!

使用 Sass 函数

通过使用 Sass,我们可以通过将 SVG 的源代码直接复制到我们的代码库中来简化我们的工作,让 Sass 正确地对它们进行编码,从而避免任何浏览器错误。

此解决方案主要受 Threespot Media 开发的现有函数的启发,该函数可在 他们的存储库 中找到。

以下是此技术的四个步骤

  • 创建一个包含所有 SVG 图标的变量。
  • 列出数据 URI 需要跳过的所有字符。
  • 实现一个函数将 SVG 编码为数据 URI 格式。
  • 在您的代码中使用您的函数。

1. 图标列表

/**
* Add all the icons of your project in this Sass list
*/
$svg-icons: (
  burger: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.8 18.92" width="24.8" height="18.92"><path d="M23.8,9.46H1m22.8,8.46H1M23.8,1H1" fill="none" stroke="#000" stroke-linecap="round" stroke-width="2"/></svg>'
);

2. 转义字符列表

/**
* Characters to escape from SVGs
* This list allows you to have inline CSS in your SVG code as well
*/
$fs-escape-chars: (
  ' ': '%20',
  '\'': '%22',
  '"': '%27',
  '#': '%23',
  '/': '%2F',
  ':': '%3A',
  '(': '%28',
  ')': '%29',
  '%': '%25',
  '<': '%3C',
  '>': '%3E',
  '\\': '%5C',
  '^': '%5E',
  '{': '%7B',
  '|': '%7C',
  '}': '%7D',
);

3. 编码函数

/**
* You can call this function by using `svg(nameOfTheSVG)`
*/
@function svg($name) {
  // Check if icon exists
  @if not map-has-key($svg-icons, $name) {
    @error 'icon “#{$name}” does not exists in $svg-icons map';
    @return false;
  }

  // Get icon data
  $icon-map: map-get($svg-icons, $name);

  $escaped-string: '';
  $unquote-icon: unquote($icon-map);
  // Loop through each character in string
  @for $i from 1 through str-length($unquote-icon) {
    $char: str-slice($unquote-icon, $i, $i);

    // Check if character is in symbol map
    $char-lookup: map-get($fs-escape-chars, $char);

    // If it is, use escaped version
    @if $char-lookup != null {
        $char: $char-lookup;
    }

    // Append character to escaped string
    $escaped-string: $escaped-string + $char;
  }

  // Return inline SVG data
  @return url('data:image/svg+xml, #{$escaped-string} ');
}		

4. 在您的页面中添加 SVG

button {
  &::after {
    /* Import inline SVG */
    content: svg(burger);
  }
}

如果您按照这些步骤操作,Sass 应该正确编译您的代码并输出以下内容

button::after {
  content: url("data:image/svg+xml, %3Csvg%20xmlns=%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox=%270%200%2024.8%2018.92%27%20width=%2724.8%27%20height=%2718.92%27%3E%3Cpath%20d=%27M23.8,9.46H1m22.8,8.46H1M23.8,1H1%27%20fill=%27none%27%20stroke=%27%23000%27%20stroke-linecap=%27round%27%20stroke-width=%272%27%2F%3E%3C%2Fsvg%3E ");
}		

自定义属性

现在实现的 Sass svg() 函数运行良好。但它最大的缺陷是,如果代码中多个地方需要一个图标,就会被重复,从而可能大大增加编译后的 CSS 文件的体积!

为了避免这种情况,我们可以将所有图标存储在 CSS 变量 中,并使用对变量的引用,而不是每次都输出编码后的 URI。

我们将保留之前相同的代码,但这次我们将首先将 Sass 列表中的所有图标输出到网页的根目录

/**
  * Convert all icons into custom properties
  * They will be available to any HTML tag since they are attached to the :root
  */

:root {
  @each $name, $code in $svg-icons {
    --svg-#{$name}: #{svg($name)};
  }
}

现在,我们不必每次需要图标时都调用 svg() 函数,而要使用以 --svg 为前缀创建的变量。

button::after {
  /* Import inline SVG */
  content: var(--svg-burger);
}

优化您的 SVG

此技术不会对您使用的 SVG 源代码进行任何优化。请确保您没有留下不必要的代码;否则它们也会被编码,从而增加您的 CSS 文件大小。

您可以查看 此很棒的工具列表,其中包含有关如何正确优化 SVG 的工具和信息。我最喜欢的工具是 Jake Archibald 的 SVGOMG - 只需将您的文件拖放到其中,然后复制输出的代码。

额外:在悬停时更新图标

使用此技术,我们无法使用 CSS 选择 SVG 的特定部分。例如,没有办法在用户将鼠标悬停在按钮上时更改图标的 fill 颜色。但是,我们可以使用 CSS 做一些技巧,以便仍然能够修改图标的外观。

例如,如果您有一个黑色图标,并且希望它在悬停时变为白色,您可以使用 invert() CSS 过滤器。我们也可以使用 hue-rotate() 过滤器。

额外 #2:使用 CSS mask-image 属性更新图标

另一个可以更改图标颜色的技巧是将其用作伪元素的蒙版,该伪元素具有背景。将您的伪元素设置为 inline-block,并具有 background-color,并为所需大小定义 widthheight

拥有具有所需颜色的矩形后,将这四个值应用于仅保留所需 SVG 形状。

  • mask-image: var(--svg-burger): 对我们图标的引用。
  • mask-repeat: no-repeat: 防止蒙版重复。
  • mask-size: contain: 使图标完美地适合矩形。
  • mask-position: center: 将我们的图标居中在伪元素中。

不要忘记,截至 2022 年 9 月,所有 CSS mask 属性仍然需要以 -webkit- 为前缀,才能在大多数浏览器中使用。

感谢 ChristopherMike 在评论中告诉我这个技巧!

就是这样!

我希望您发现这个小助手函数在您自己的项目中很有用。请告诉我您对这种方法的看法 - 我很想知道您如何改进它或以不同的方式处理它!