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

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

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

字体是矢量,因此你无须担忧分辨率的问题。他们和文本同样由于拥有 CSS 属性,那就意味着,你彻底能够应用 sizecolorstyle 。你能够添加转换、特效和装饰,好比旋转、下划线或者阴影。前端

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

但是图标字体并不完美, 这就是为何愈来愈多的人使用行内 SVG 。CSS Tricks 写了图标字体劣于原生 SVG 元素的地方:锐利度、定位或者是由于跨域加载、特定浏览器错误和广告屏蔽器等缘由致使的失败。如今你能够规避绝大多数这些问题了,整体上使用图标字体是一个安全的选择。ios

然而,仍是有一件事情对于图标字体来讲是绝对不可能的:多色支持。只有 SVG 能够作到。git

摘要 :这篇博文深刻阐述怎么作和为何。若是你想理解整个思惟过程,推荐阅读。不然你能够直接在 CodePen 看最终代码。github

设置 SVG 标志图标

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

经过 SVG 符号图标,你只需拥有一个 SVG 元素,而后在每一个须要的地方引用就行了。chrome

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

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path d="..." />
  </symbol>
</svg>
复制代码

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

而后,全部你要作的是用一个 <use> 标签将图标实例化。

<svg>
  <use xlink:href="#my-first-icon" />
</svg>
复制代码

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

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

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

须要提到的是 xlink:href 是一个弃用的 SVG 属性。尽管大多数浏览器仍然支持,你应该用 **href** 替代。如今的问题是,一些浏览器好比 Safari 不支持使用 href 进行 SVG 资源引用,所以你仍然须要提供 xlink:href 选项。

安全起见,两个都用吧。

添加一些颜色

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

// HTML
<svg class="icon">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
  fill: red;
}
复制代码

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

// HTML
<svg class="icon icon-red">
  <use xlink:href="#my-first-icon" />
</svg>
<svg class="icon icon-blue">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon {
  width: 100px;
  height: 100px;
}
.icon-red {
  fill: red;
}
.icon-blue {
  fill: blue;
}
复制代码

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

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

// HTML
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path class="path1" d="..." />
    <path class="path2" d="..." />
    <path class="path3" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors .path1 {
  fill: red;
}
.icon-colors .path2 {
  fill: green;
}
.icon-colors .path3 {
  fill: blue;
}
复制代码

不起做用。

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

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

CSS 变量拯救世界

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

在咱们以前的例子里,咱们继承了填充属性。回头看,你会看到咱们声明填充颜色的类被附加在了实例上,而不是定义上。这就是咱们可以为同必定义的每一个不一样实体赋予不一样颜色的缘由。

如今有个问题:咱们想传递不一样颜色给原始 SVG 的不一样路径,可是只能从一个 fill 属性里继承。

这就须要 CSS 变量了。

就像任何其它属性同样, CSS 变量在规则集里被声明。你能够用任意命名,分配任何有效的 CSS 值。而后,你为它本身或者其它子属性,像一个值同样声明它,而且这将被继承

.parent {
  --custom-property: red;
  color: var(--custom-property);
}
复制代码

全部 .parent 的子项都有红色文本。

.parent {
  --custom-property: red;
}
.child {
  color: var(--custom-property);
}
复制代码

全部嵌套在 .parent 标签里的 .child 都有红色文本。

如今,让咱们把这个概念应用到 SVG 符号里去。咱们将在 SVG 定义的每一个部分使用 fill 属性,而且设置成不一样的 CSS 变量。而后,咱们将给它们分配不一样的颜色。

// HTML
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1)" d="..." />
    <path fill="var(--color-2)" d="..." />
    <path fill="var(--color-3)" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors {
  --color-1: #c13127;
  --color-2: #ef5b49;
  --color-3: #cacaea;
}
复制代码

而后… 生效了 🎉!

如今开始,为了用不一样的颜色方案建立实例,咱们所须要作的是建立一个新类。

// HTML
<svg class="icon icon-colors-alt">
  <use xlink:href="#my-first-icon" />
</svg>
// CSS
.icon-colors-alt {
  --color-1: brown;
  --color-2: yellow;
  --color-3: pink;
}
复制代码

若是你仍然想有单色图标,你没必要在每一个 CSS 变量中重复一样的颜色。相反,你能够声明一个单一 fill 规则:由于若是 CSS 变量没有被定义,它将会回到你的 fill 声明。

.icon-monochrome {
  fill: grey;
}
复制代码

你的 fill 声明将会生效,由于初始 SVG 的 fill 属性被未设置的 CSS 变量值定义。

怎样命名个人 CSS 变量?

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

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

问题是,在咱们的案例里,咱们不能把零散的类应用于咱们想设置样式的标签。实用主义原则不能应用,由于咱们对于每一个图标有单独的引用,咱们不得不经过类的变化来设置样式。

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

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

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

有两个方法能够实现::rootvar() default

:root

:root 选择器中你能够定义全部你的 CSS 变量。这将会把它们统一放在一个位置,容许你『分享』类似的颜色。 :root 拥有最低的优先度,所以能够很容易地被重写。

: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;
}
复制代码

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

大多数时候,当一个图标只用一种颜色,我用 --fill-color 名称。简单,易懂,对于全部仅须要一种颜色的图标很是有意义。若是我必须在 :root 声明中声明全部变量,我就不会有几个 --fill-color。我将会被迫定义 --fill-color-1--fill-color-2 或者使用相似 --star-fill-color--cup-fill-color 的命名空间。

var() 默认

你能够用 var() 功能来把一个 CSS 变量分配给一个属性,而且它的第二个参数能够设置为某个默认值。

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1, red)" d="..." />
    <path fill="var(--color-2, blue)" d="..." />
    <path fill="var(--color-3, green)" d="..." />
  </symbol>
</svg>
复制代码

在你定义完成 --color-1--color-2--color-3 以前,图标将会使用你为每一个 <path> 设置的默认值。这解决了当咱们使用 :root 时的全局关联问题,可是请当心:你如今有一个默认值,而且它将会生效。结果是,你不再能使用单一的 fill 声明来定义单色图标了。你将不得不一个接一个地给每一个使用于这个图标的 CSS 变量分配颜色。

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

How browser-friendly is all that?

CSS 变量与大多数现代浏览器兼容,可是就像你想的那样, Internet Explorer 彻底不兼容。由于微软要支持 Edge 终止了 IE11 开发, IE 之后也没有机会遇上时代了。

如今,仅仅是由于一个功能不被某个浏览器(而你必须适配)兼容,这不意味着你必须全盘放弃它。在这种状况下,考虑下优雅降级:给现代浏览器提供多彩图标,给落后浏览器提供备份的填充颜色。

你想要作的是设置一个仅在 CSS 变量不被支持时触发的声明。这能够经过设置备份颜色的 fill 属性实现:若是 CSS 变量不被支持,它甚至不会被归入考虑。若是它们不能被支持,你的 fill 声明将会生效。

若是你使用 Sass 的话,这个能够被抽象为一个 @mixin

@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;
  };
}
复制代码

在 mixin 中经过 @content 传递 CSS 变量也是一个可选项。若是你在外面作这件事,被编译的 CSS 将会变得同样。可是它有助于被打包在一块儿:你能够在你编辑器中折叠片断而后用眼睛分辨在一块儿的声明。

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

若是你想了解更多关于 SVG 符号图标(或者通常的 SVG ),我强烈建议你阅读 Sara Soueidan 写的一切东西。若是你有任何关于 CSS 符号图标的问题,不要犹豫,尽管在 Twitter 上联系我。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索