高清屏下图标边缘像狗啃,hover换个颜色还得重新切图,更别提动画效果了。SVG格式虽然公认是图标界的天花板,但每次往HTML里塞一堆<svg>代码,看着就头大。有一种操作:把SVG转成数据URI,再用Sass函数批量编码,最后扔进CSS自定义属性里。这样CSS文件就能直接调用图标,HTML保持干净,还能随便改大小和滤镜颜色。下面直接上硬核流程,手把手教搭建这套图标库。
准备图标库
先建一个Sass地图(map),把项目里所有图标的原始SVG代码都塞进去。每个图标给个外号,比如burger代表汉堡菜单图标。
// 图标仓库,键名随便起,值必须是完整的SVG字符串
$icon-library: (
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>',
downArrow: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 16L6 10h12z" fill="currentColor"/></svg>'
);代码里的fill="currentColor"是个神技,这样图标颜色就能继承父元素的文字颜色,后面不用额外写滤镜。但注意,如果SVG里写死了stroke="#000",那就只能用其他黑科技改色了。另外,SVG代码越精简越好,别留什么编辑器自带的注释或多余分组,否则编码后CSS文件会膨胀得像猪肚。
转义字符表
数据URI要求特殊字符必须转义,否则浏览器解析会崩。空格、双引号、井号、斜杠这些全得换成百分号编码。搞一张转义对照表,Sass函数会遍历每个字符并替换。
$escape-char-map: (
' ': '%20',
'"': '%22',
'#': '%23',
'/': '%2F',
':': '%3A',
'(': '%28',
')': '%29',
'%': '%25',
'<': '%3C',
'>': '%3E',
'\': '%5C',
'^': '%5E',
'{': '%7B',
'|': '%7C',
'}': '%7D',
);这里有个坑:如果SVG代码里本来就用了%号(比如某些滤镜),必须把它也转成%25,不然双重编码会乱套。另外,换行符和制表符最好提前手动删掉,虽然不转义也能用,但会增加体积。建议把整个SVG字符串压缩成一行,连回车都不要留。
编写编码函数
写一个Sass函数,传入图标名,自动从库中取出代码,逐个字符查表转义,最后拼成url('data:image/svg+xml, ...')格式。
@function encode-svg($icon-name) {
// 检查图标名是否存在
@if not map-has-key($icon-library, $icon-name) {
@error '图标 “#{$icon-name}” 不在库中,检查一下键名';
@return false;
}
$raw-svg: map-get($icon-library, $icon-name);
$unquoted-svg: unquote($raw-svg);
$escaped: '';
// 逐字符处理
@for $i from 1 through str-length($unquoted-svg) {
$char: str-slice($unquoted-svg, $i, $i);
$replacement: map-get($escape-char-map, $char);
@if $replacement != null {
$char: $replacement;
}
$escaped: $escaped + $char;
}
@return url('data:image/svg+xml, #{$escaped}');
}这个函数相当于给SVG穿了一层防护服,特殊符号全被屏蔽。不过注意,如果SVG代码里包含<style>标签内的CSS,里面的花括号{}也要被转义,幸好转义表已经覆盖了。但有一个副作用:转义后的字符串长度暴涨,一个小图标可能膨胀两倍。所以只适合图标数量少(比如10个以内)的项目,要是塞上百个复杂图标,CSS文件会变得比砖头还重。
存入CSS变量
把所有图标丢进:root,每个图标对应一个自定义属性(CSS变量)。变量名统一用--svg-加图标名,值为编码后的数据URI。
:root {
@each $name, $code in $icon-library {
--svg-#{$name}: #{encode-svg($name)};
}
}编译后,浏览器里会生成一堆类似--svg-burger: url("data:image/svg+xml, %3Csvg...");的变量。这些变量挂在全局,任何选择器都能用var(--svg-burger)直接召唤图标。这样做的好处是,同一个图标在多处使用时,编译出来的CSS只有一行变量引用,而不是重复输出几十KB的编码字符串。举个例子:十个地方调用汉堡图标,传统函数方式会生成十份相同的url(...),而变量方式只生成一份定义,CSS体积瞬间瘦身。
调用图标
伪元素的content属性可以接收CSS变量,直接把图标当成文字内容插进去。比如给按钮的:after加个菜单图标:
.menu-btn::after {
content: var(--svg-burger);
display: inline-block;
width: 24px;
height: 24px;
// 调整大小可以通过font-size或宽高,但content中的SVG默认按原始尺寸渲染
}但这里有个大坑:content里的SVG默认不会继承宽高,需要给伪元素设置display: inline-block和具体的宽高。而且content生成的图片无法直接改颜色(除非SVG里用了currentColor)。想换颜色?用滤镜!比如hover时变成白色:
.menu-btn:hover::after {
filter: invert(1); // 黑变白,简单粗暴
}或者用brightness(0) invert(1)组合拳。不过滤镜会连阴影、边框一起反色,复杂图标可能会翻车。
备用遮罩法
如果嫌弃滤镜不够精准,可以换mask(蒙版)方案。把图标作为蒙版图片,背景色随便换。
.icon-mask {
display: inline-block;
width: 32px;
height: 32px;
background-color: #ff6600; // 想要什么颜色就设什么色
mask-image: var(--svg-burger);
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
-webkit-mask-image: var(--svg-burger); // 老浏览器需要前缀
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: contain;
-webkit-mask-position: center;
}这个骚操作相当于用SVG的形状在彩色矩形上挖了个洞,透出背景色。可以随心所欲换颜色,甚至加渐变色背景。但注意,mask属性在某些安卓老机型上翻车,而且需要双写带-webkit-前缀的版本(2024年依然如此)。另外,mask方式不支持多色图标,只能显示单色,因为背景色是统一的。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 滤镜 | 代码少 | 全图变色 |
| 遮罩 | 颜色精准 | 仅单色 |
优化SVG源码
编码前一定先把SVG丢进优化工具(比如SVGOMG)里榨干水分。不必要的元数据、注释、空分组、默认数值全删掉。一个未优化的SVG可能几百字节,优化后直接腰斩。举个例子:
原始<path d="..." fill="#000000"/>可以改成fill="#000",stroke-width="2.0000"改成stroke-width="2"。还有<g>标签如果能合并就合并,能省不少字符。这些优化动作必须在编码前完成,否则转义后的小垃圾也会原封不动留在CSS里。
