使用 SVG 符号和 CSS 变量实现多彩图标

使用 SVG 符号和 CSS 变量实现多彩图标

使用图片和 CSS 精灵制作 web 图标的日子一去不复返了。随着 web 字体的爆发,图标字体已经成为在你的 web 项目中显示图标的第一解决方案。

字体是矢量,所以你无须担心分辨率的问题。他们和文本一样因为拥有 CSS 属性,那就意味着,你完全可以应用 <code>size/<code> 、 <code>color/<code> 和 <code>style/<code> 。你可以添加转换、特效和装饰,比如旋转、下划线或者阴影。

怪不得类似 Font Awesome 这类项目仅仅在 npm 至今已经被下载了超过 1500 万次。

可是图标字体并不完美, 这就是为什么越来越多的人使用行内 SVG 。CSS Tricks 写了图标字体劣于原生 SVG 元素的地方:锐利度、定位或者是因为跨域加载、特定浏览器错误和广告屏蔽器等原因导致的失败。现在你可以规避绝大多数这些问题了,总体上使用图标字体是一个安全的选择。

然而,还是有一件事情对于图标字体来说是绝对不可能的:多色支持。只有 SVG 可以做到。

设置 SVG 标志图标

行内 SVG 的问题是,它会非常冗长。你肯定不想每次使用同一个图标的时候,还需要复制/粘贴所有坐标。这将会非常重复,很难阅读,更难维护。

通过 SVG 符号图标,你只需拥有一个 SVG 元素,然后在每个需要的地方引用就好了。

先添加行内 SVG ,隐藏它之后,再用 <code><symbol>/<code> 包裹它,用 <code>id/<code> 对其进行识别。

<symbol> <title>my-first-icon/<title> <path> /<symbol>

整个 SVG 标记被一次性包裹并且在 HTML 中被隐藏。

然后,所有你要做的是用一个 <code>/<code> 标签将图标实例化。

这将会显示一个初始 SVG 图标的副本。

**就是这样了!**看起来很棒,是吧?

你可能注意到了这个有趣的 <code>xlink:href/<code> 属性:这是你的实例与初始 SVG 之间的链接。

需要提到的是 <code>xlink:href/<code> 是一个弃用的 SVG 属性。尽管大多数浏览器仍然支持,你应该用 <code>**href**/<code> 替代。现在的问题是,一些浏览器比如 Safari 不支持使用 <code>href/<code> 进行 SVG 资源引用,因此你仍然需要提供 <code>xlink:href/<code> 选项。

安全起见,两个都用吧。

添加一些颜色

不像是字体, <code>color/<code> 对于 SVG 图标没有任何作用:你必须使用 <code>fill/<code> 属性来定义一个颜色。这意味着他们将不会像图标字体一样继承父文本颜色,但是你仍然可以在 CSS 中定义它们的样式。

// HTML // CSS .icon { width: 100px; height: 100px; fill: red; }

在这里,你可以使用不同的填充颜色创建同一个图标的不同实例。

// HTML // CSS .icon { width: 100px; height: 100px; } .icon-red { fill: red; } .icon-blue { fill: blue; }

这样就可以生效了,但是不完全符合我们的预期。目前为止,我们所有做的事情可以使用一个普通的图标字体来实现。我们想要的是在图标的位置可以有不同的颜色。我们想要向每个路径上填充不同颜色,而不需要改变其他实例,我们想要能够在必要的时候重写它。

首先,你可能会受到依赖于特性的诱惑。

// HTML <symbol> <title>my-first-icon/<title> <path> <path> <path> /<symbol> // CSS .icon-colors .path1 { fill: red; } .icon-colors .path2 { fill: green; } .icon-colors .path3 { fill: blue; }

不起作用。

我们尝试设置 <code>.path1/<code> 、 <code>.path2/<code> 和 <code>.path3/<code> 的样式,仿佛他们被嵌套在 <code>.icon-colors/<code> 里,但是严格来说,并非如此。 <code>/<code> 标签不是一个会被你的 SVG 定义替代的占位符。这是一个引用将它所指向内容复制为 shadow DOM

**那接下来我们该怎么办?**当子项不在 DOM 中时,我们如何才能用一个区域性的方式影响子项?

CSS 变量拯救世界

在 CSS 中,一些属性从父元素继承给子元素。如果你将一个文本颜色分配给 <code>body/<code> ,这一页中所有文本将会继承那个颜色直到被重写。父元素没有意识到子元素,但是可继承的样式仍然继续传播。

现在有个问题:我们想传递不同颜色给原始 SVG 的不同路径,但是只能从一个 <code>fill/<code> 属性里继承。

这就需要 CSS 变量了。

.parent { --custom-property: red; color: var(--custom-property); }

所有 <code>.parent/<code> 的子项都有红色文本。

.parent { --custom-property: red; } .child { color: var(--custom-property); }

所有嵌套在 <code>.parent/<code> 标签里的 <code>.child/<code> 都有红色文本。

现在,让我们把这个概念应用到 SVG 符号里去。我们将在 SVG 定义的每个部分使用 <code>fill/<code> 属性,并且设置成不同的 CSS 变量。然后,我们将给它们分配不同的颜色。

// HTML <symbol> <title>my-first-icon/<title> <path> <path> <path> /<symbol> // CSS .icon-colors { --color-1: #c13127; --color-2: #ef5b49; --color-3: #cacaea; }

然后… 生效了 !

现在开始,为了用不同的颜色方案创建实例,我们所需要做的是创建一个新类。

// HTML // CSS .icon-colors-alt { --color-1: brown; --color-2: yellow; --color-3: pink; }

.icon-monochrome { fill: grey; }

怎样命名我的 CSS 变量?

当提到在 CSS 中命名,通常有两条途径:描述的或者语义的。描述的意思是告诉一个颜色是什么:如果你存储了 <code>#ff0000/<code> 你可以叫它 <code>--red/<code> 。语义的意思是告诉颜色它将会被如何应用:如果你使用 <code>#ff0000/<code> 来给一个咖啡杯把手赋予颜色,你可以叫它 <code>--cup-handle-color/<code> 。

描述的命名也许是你的本能。看起来更干脆,因为<code>#ff0000/<code> 除了咖啡杯把手还有更多地方可以被使用。一个 <code>--red/<code> CSS 变量可被复用于其他需要变成红色的图标路径。毕竟,这是实用主义在 CSS 中的工作方式。并且是一个良好的系统。

问题是,在我们的案例里,

我们不能把零散的类应用于我们想设置样式的标签。实用主义原则不能应用,因为我们对于每个图标有单独的引用,我们不得不通过类的变化来设置样式。

使用语义类命名,例如 <code>--cup-handle-color/<code> ,对于这个情况更有用。当你想改变图标一部分的颜色时,你立即知道这是什么以及需要重写什么。无论你分配什么颜色,类命名将会一直关联。

默认还是不要默认,这是个问题

将你的图标的多色版本设置成默认状态是很有诱惑力的选择。这样,你无需设置额外样式,只需要在必要的时候可以添加你自己的类。

有两个方法可以实现::rootvar() default

:root

在 <code>:root/<code> 选择器中你可以定义所有你的 CSS 变量。这将会把它们统一放在一个位置,允许你『分享』相似的颜色。 <code>:root/<code> 拥有最低的优先度,因此可以很容易地被重写。

:root { --color-1: red; --color-2: green; --color-3: blue; --color-4: var(--color-1); } .icon-colors-alt { --color-1: brown; --color-2: yellow; --color-3: pink; --color-4: orange; }

然而,

这个方法有一个主要缺点。首先,将颜色定义与各自的图标分离可能会有些让人疑惑。当你决定重写他们,你必须在类与 <code>:root/<code> 选择器之间来回操作。但是更重要的是,它不允许你去关联你的 CSS 变量,因此让你不能复用同一个名字。

var() 默认

你可以用 <code>var()/<code> 功能来把一个 CSS 变量分配给一个属性,并且它的第二个参数可以设置为某个默认值。

<symbol> <title>my-first-icon/<title> <path> <path> <path> /<symbol>

设置默认值会很有用,但是这是一个折中方案。我建议你不要形成习惯,只在对给定项目有帮助的时候做这件事情。

How browser-friendly is all that?

CSS 变量与大多数现代浏览器兼容,但是就像你想的那样, Internet Explorer 完全不兼容。因为微软要支持 Edge 终止了 IE11 开发, IE 以后也没有机会赶上时代了。

现在,仅仅是因为一个功能不被某个浏览器(而你必须适配)兼容,这不意味着你必须全盘放弃它。在这种情况下,考虑下优雅降级

:给现代浏览器提供多彩图标,给落后浏览器提供备份的填充颜色。

如果你使用 Sass 的话,这个可以被抽象为一个 <code>@mixin/<code> 。

@mixin icon-colors($fallback: black) { fill: $fallback; @content; }

现在,你可以任意定义颜色方案而无需考虑浏览器兼容问题了。

.cup { @include icon-colors() { --cup-color: red; --smoke-color: grey; }; } .cup-alt { @include icon-colors(green) { --cup-color: green; --smoke-color: grey; }; }

在不同的浏览器中查看这个 pen 。在最新版本的 Firefox , Chrome 和 Safari 中,最后两只杯子各自拥有红色杯身灰色烟气和蓝色杯身灰色烟气。在 IE 和 版本号小于 15 的 Edge 中,第三个杯子的杯身与烟气全部都是红色,第四个则全部是蓝色! ✨