胖蔡说技术
随便扯扯

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

SVG是网站上图标的最佳格式,这一点毋庸置疑。无论屏幕像素密度如何,它都允许您拥有清晰的图标,您可以在悬停时更改SVG的样式,甚至可以使用CSS或JavaScript对图标进行动画处理。

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

我有一个Sass列表,里面有我图标的所有源代码。然后,每个图标都用Sass函数编码到一个数据URI中,并存储在页面根上的自定义属性中。

我在这里为您提供的是一个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;
}

示例

// html代码
<button>Menu</button>

//scss代码
/*
* Sass functions forked from https://github.com/Threespot/frontline-sass/blob/3a7c6de247bb031eeb437846c0c53758dc4c31ec/src/functions/_svg-url.scss
*/

/**
* List of all the SVG icons of the project
*/
$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>'
);

/**
* Characters to escape from SVGs
* Source: https://github.com/Threespot/frontline-sass/blob/master/src/variables/_escape-chars.scss
*/
$fs-escape-chars: (
    ' ': '%20',
    '\'': '%22',
    '"': '%27',
    '#': '%23',
    '/': '%2F',
    ':': '%3A',
    '(': '%28',
    ')': '%29',
    '%': '%25',
    '<': '%3C',
    '>': '%3E',
    '\\': '%5C',
    '^': '%5E',
    '{': '%7B',
    '|': '%7C',
    '}': '%7D',
);

/**
 * Helper to get URL-escaped inline SVG code
 */
@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} ');
}

/**
 * Convert all icons into custom properties
 */

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

button {
  margin: 1rem;
  font-size: 20px;
  padding: 10px 25px;
  border-radius: 10px;
  border: 2px solid black;
  background: none;
  &::after {
    /* Import inline SVG */
    content: var(--svg-burger);
    vertical-align: middle;
    margin-left: 1rem;
  }
}

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>标记的外部文件或在带有url(path/to/file.SVG)CSS中加载复杂的SVG

将SVG编码为数据URI

将SVG编码为数据URI并不是什么新鲜事。事实上,Chris Coyier在10多年前写了一篇关于这项技术的帖子,解释了如何使用这项技术以及为什么你应该(或不应该)使用它。

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

  • 作为外部图像(使用背景图像、边框图像、列表样式图像等)
  • 作为伪元素的内容(例如::before或::after)

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

<ul>
  <li>Super Tomato</li>
  <li>Captain Spinach</li>
</ul>
<button>Hover me</button>

ul {
  list-style-image: url("data:image/svg+xml, %3Csvg%20xmlns=%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox=%270%200%2040%2038.4%27%20width=%2740%27%20height=%2738.4%27%3E%3Cpath%20d=%27m16%2022-4-5a3%203%200%200%201%200-4%2012%2012%200%200%201%203-2C8%2014%201%2020%200%2021s5%202%206%208c1%205%208%207%209%205a43%2043%200%200%201%203-7v-4a3%203%200%200%201-2-1ZM38%209H24a13%2013%200%200%200-10%205%202%202%200%200%200%200%202l3%205a2%202%200%200%200%201%201%202%202%200%200%200%201-1h1v1l-1%2014a2%202%200%201%200%204%200l1-13h1l1%2013a2%202%200%200%200%202%202%202%202%200%200%200%202-2l-1-14v-9h9a2%202%200%200%200%202-2%202%202%200%200%200-2-2ZM20%2019l-2-4a10%2010%200%200%201%202-1Zm8-15a4%204%200%201%201-4-4%204%204%200%200%201%204%204%27%20%2F%3E%3C%2Fsvg%3E ");
  margin: 1rem;
  li {
    font-size: 1.8rem;
    padding-left: 1rem;
    margin-bottom: 1rem;
  }
}

button {
  margin: 1rem;
  font-size: 20px;
  padding: 10px 25px;
  border-radius: 10px;
  border: 2px solid black;
  background: none;
  &::after {
    content: url("data:image/svg+xml, %3Csvg%20width=%277%27%20height=%2712.19%27%20xmlns=%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox=%270%200%207%2012.19%27%3E%3Cpath%20d=%27M.27,10.64a.89.89,0,0,0,0,1.28.91.91,0,0,0,1.28,0L6.73,6.73a.89.89,0,0,0,0-1.28L1.55.27A.89.89,0,0,0,.27.27a.89.89,0,0,0,0,1.28L4.81,6.1Z%27%2F%3E%3C%2Fsvg%3E ");
    margin-left: 10px;
    transition: 0.2s ease-out;
  }
  &:hover::after {
    margin-left: 20px;
  }
}

这个特定实现的主要问题是,每次需要新图标时,都必须手动转换SVG,而在CSS中有这么长的不可读代码串并不是很愉快。

使用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的特定部分。例如,当用户悬停按钮时,无法更改图标的填充颜色。但是,我们可以使用CSS来修改图标的外观。

例如,如果你有一个黑色的图标,并且你想在悬停时让它变成白色,你可以使用inverse()CSS过滤器。我们也可以使用hue-rotate()过滤器。

<p>All those buttons are using the same SVG icon</p>
<button class="burger burger--negative">Negative button</button><br>
<button class="burger burger--color burger--green">Green button</button><br>
<button class="burger burger--color burger--blue">Blue button</button>

/*
* Sass functions forked from https://github.com/Threespot/frontline-sass/blob/3a7c6de247bb031eeb437846c0c53758dc4c31ec/src/functions/_svg-url.scss
*/

/**
* List of all the SVG icons of the project
*/
$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="#f00" stroke-linecap="round" stroke-width="2"/></svg>'
);

使用CSS mask image属性更新图标

另一个能够改变图标颜色的技巧是将其用作带有背景的伪元素上的遮罩。将伪元素设置为具有背景颜色的内联块,并定义所需大小的宽度和高度。

一旦您有了一个具有所需颜色的矩形,请应用这四个值以仅保持所需SVG的形状:

  • mask-image:var(–svg-burger):对我们图标的引用。
  • mask-repeat:不重复:防止蒙版被复制。
  • mask-size:包含:使图标完全适合矩形。
  • mask-position:centre:将我们的图标放在伪元素的中心。

不要忘记,截至2022年9月,大多数浏览器的所有CSS掩码属性仍然需要以-webkit为前缀。

<p>All those buttons are using the same SVG icon used as mask-image</p>
<button class="burger">Negative button</button><br>
<button class="burger burger--green">Green button</button><br>
<button class="burger burger--blue">Blue button</button>
/*
* Sass functions forked from https://github.com/Threespot/frontline-sass/blob/3a7c6de247bb031eeb437846c0c53758dc4c31ec/src/functions/_svg-url.scss
*/

/**
* List of all the SVG icons of the project
*/
$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="#fff" stroke-linecap="round" stroke-width="2"/></svg>'
);

/**
* Characters to escape from SVGs
* Source: https://github.com/Threespot/frontline-sass/blob/master/src/variables/_escape-chars.scss
*/
$fs-escape-chars: (
  " ": "%20",
  "'": "%22",
  '"': "%27",
  "#": "%23",
  "/": "%2F",
  ":": "%3A",
  "(": "%28",
  ")": "%29",
  "%": "%25",
  "<": "%3C",
  ">": "%3E",
  "\\":"%5C",
  "^": "%5E",
  "{": "%7B",
  "|": "%7C",
  "}": "%7D"
);

/**
 * Helper to get URL-escaped inline SVG code
 */
@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} ");
}

/**
 * Convert all icons into custom properties
 */

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

.burger {
  margin: 1rem;
  font-size: 20px;
  padding: 10px 25px;
  border-radius: 10px;
  border: 2px solid black;
  background: none;
  transition: 0.15s ease-out;
  border-color: currentColor;
  &:hover {
    background: #000;
    color: white;
  }
  &::after {
    content: "";
    /* Import inline SVG */
    -webkit-mask-image: var(--svg-burger);
    mask-image: var(--svg-burger);
    
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    
    -webkit-mask-size: contain;
    mask-size: contain;
    
    -webkit-mask-position: center;
    mask-position: center;
    
    width: 24px;
    height: 24px;
    display: inline-block;
    background: currentColor;
    vertical-align: middle;
    margin-left: 1rem;
    transition: inherit;
  }
}
.burger--green {
  color: #007a00;
  &:hover {
    background: #007a00;
  }
}
.burger--blue {
  color: #009cff;
  &:hover {
    background: #009cff;
  }
}
赞(0) 打赏
转载请附上原文出处链接:胖蔡说技术 » 我是如何用CSS自定义属性制作图标系统的
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏