参考地址 用CSS给SVG 的内容添加样式
一篇深入探究如何给SVG
<use>
元素的内容添加样式的文章,并针对碰到的问题逐一解决。
SVG图形的一个最常见用例是图标系统,其中最常用的SVG sprite技术就是使用SVG<use>
元素在文档中任意位置“实例化”图标。
使用<use>
元素实例化图标或任何其它的SVG元素或图像,给元素添加样式时经常会碰到一些问题。这篇文章的目的是尽可能给你介绍一些方法来解决:使用<use>
引入的内容添加样式受限的问题。
但是在开始之前,我们先快速浏览一下SVG的主要结构和分组元素,然后慢慢进入use
的世界中,以及shadow DOM,然后重回CSS的怀抱。我们会逐步讲解为什么给<use>
内容添加样式会比较麻烦,以及有什么好的解决方案。
SVG结构化、分组,以及在SVG中引用(重用)元素速览
SVG中有四个主要的元素用于在文档中定义、结构化和引用SVG代码。这些元素使得重用SVG元素变得容易,同时保持代码的简洁性和可读性。因为SVG的特性,这些元素和图形编辑器中的某些命令具有相同的功能。
这四个用于SVG分组和引用的主要元素是:<g>
, <defs>
, <use>
和 <symbol>
。
<g>
元素(“group”的简写),用于给逻辑上相联系的图形元素分组。从图形编辑器的角度,如Adobe Illustrator,<g>
元素提供了类似于分组对象的功能。你也可以把它想象成图形编辑器中图层的概念,因为一层也是一组元素。
当你想要应用某个样式,并希望这个样式能被组中的所有元素继承,分组元素<g>
非常好用,特别是当你想要给某组元素应用动画,同时还需要保持它们彼此的空间关系的时候。
<defs>
元素用来定义你之后要重用的元素。当你想要创建某一类在文档中要多次使用的“模板”时,使用<defs>
定义元素。在<defs>
元件中定义的元素不会在画布中渲染出来,除非你在文档的某个位置调用了它们。
<defs>
可以用于定义很多东西,但是最主要的使用情景之一是定义类似渐变的图案,例如,使用这些渐变作为其它SVG元素的描边填充。它可以用来定义你想要在画布上渲染的任何元素。
<symbol>
元素结合了<defs>
和<g>
元素的优点,将定义模板的元素组合在一起,以便之后在文档中的其他位置引用。和<defs>
不同,<symbol>
通常不用于定义图案,但是经常用于定义例如图标这样的标志,在整个文档中都可以被引用。
<symbol>
元素相比其它两个元素有一个非常重要的优点:它接受一个viewBox
属性,可以让它在任何视窗中自适应大小缩放渲染。
<use>
元素用于引用文档中其它位置定义的元素。你可以重用已有的元素,类似于图形编辑器中的复制粘贴功能。它可以重用单个元素,也可以重用一组用<g>
、<defs>
或<symbol>
定义的元素。
要使用一个元素,你需要通过一个标识对该元素进行引用——一个ID,即use
中的xlink:href
属性,以及用来给该元素定位的x
和y
属性。你可以给use
元素应用样式,这些样式也会级联应用到use
元素的内容中去。
<use>
的内容是什么呢?它被克隆到哪里了?CSS级联如何处理这些内容?
在我们回答这些问题之前,因为我们目前只讲了SVG结构化和分组元素,这里还有几篇值得我们继续深入学习的文章,关于viewBox
属性和<symbol>
的使用:
SVG <use>
及shadow DOM
当你使用<use>
引用元素时,代码如下:
<symbol id="my-icon" viewBox="0 0 30 30"><!-- icon content / shapes here --></symbol><use xlink:href="#my-icon" x="100" y="300" />
渲染在屏幕上的东西是内容定义在<symbol>
内的图标,但是这不是真正渲染出的内容,而是<use>
的内容,也就是<symbol>
内容的一个副本或者克隆。
但是<use>
元素只是一个元素,它是自闭合的。在use
标签的开闭区间内没有任何内容,所以<symbol>
的内容是克隆到哪里了呢?
答案是:Shadow DOM。(不知道为什么,shadow DOM总是让我想起蝙蝠侠(:зゝ∠)。)
什么是shadow DOM?
shadow DOM和常规的DOM很类似,不同之处在于shadow DOM不是主文档子树的一员,shadow DOM中的结点属于文档片段,基本上等同于另一棵结点树,不能像普通结点那样添加脚本和样式。这给了作者们一种方法来封装和包裹样式及脚本,当创建模块化组件时。如果你使用过HTML5的video
元素,或range input类型,也很好奇video控件或者范围输入组件是从哪里来的,那么你就已经接触过shadow DOM了。
在SVG<use>
元素中,引用元素的内容被复制到一个文档片段中保存,这个文档片段是由<use>
保留着。<use>
在这里就是一个shadow Host。
所以,<use>
的内容(克隆或复制那个它引用的元素的)都表示在一个shadow文档片段中。
也就是说,它们就在那里,但是并不可见。就像普通的DOM内容一样,但是并不是在“高等级”的DOM中,并不能在主文档中被CSS选择器和JavaScript选中,它们被复制到由<use>
保留的文档片段中。
现在,如果你是一个设计师,你可能会想:“ok,我了解了这东西了,但是有什么方法可以检查子文档,来看看它的真正的内容呢?”答案是:有的!你可以使用Chrome的开发者工具预览shadow DOM的内容。(现在还无法在Firefox中查看shadow DOM的内容。)但是为了完成这个,你需要先在“General”面板中勾选shadow DOM检查的选项。也就是:打开 Chrome 的开发者工具,点击右上角的“Settings”按钮勾选“Show user agent shadow DOM”。
在开发者工具中勾选了shadow DOM检查这一项之后,你可以在Elements面板中看到克隆的元素,和普通的DOM元素一样。下面的图片展示了<use>
元素引用<symbol>
元素的内容的示例。注意到有一个“#shadow-root”,而且当点开此片段的内容时——会发现它就是<symbol>
内容的副本。
检查一下这些代码,你可以看到shadow DOM和普通的DOM非常相似,除了在主文档中用CSS和JavaScript处理时有不同的特性之外。它们之间还存在其它差异,但是这一节不可能完全在讲shadow DOM,因为这真的是一个很大的概念,所以如果你想要阅读和了解更多关于它的内容的话,我推荐下面这几篇文章:
图灵社区的同学翻译的shadow DOM系列文章。
对我来说,考虑如何限制和shadow DOM的交互时,我把它当成普通DOM一样,除了在用CSS(和JavaScript)添加样式时需要不同地处理。但是对于SVG开发者来说就是一个问题:shadow DOM中<use>
的内容如何存在,当需要给内容应用样式或者改变样式的时候,因为我们希望可以为它们添加样式。使用<use>
的目的是为了可以创建某个元素的多个不同的“副本”,很多情境下,我们想要的是可以给差异化地给不同的副本添加样式。例如,考虑一个有两种样式的logo(反转颜色的主题)或多种颜色的icon,每一个都有自己的主题。这时,我们自然而然就会想到使用CSS来完成。
也就是说,我们前面提到的shadow DOM的内容在CSS看来不能像普通DOM一样添加样式。所以,我们要怎么给它的内容添加样式呢?我们不能像这样指向<use>
的路径级联:
use path#line { stroke: #009966;}
因为我们不能使用普通的CSS选择器来获取shadow DOM。
有一组特殊的选择器可以让我们打破普通DOM的界限,给它里面的结点应用样式,但是这些选择器并没有很好的浏览器支持,而且相比CSS中提供的一长串用来选中普通DOM元素的选择器,它们是受限的。
此外,我们希望有一个更简单的方式来给SVG<use>
的内容添加样式,而不需要去接触shadow DOM的具体内容——只使用简单的CSS和SVG。
为了实现以及获得更多一点控制,给<use>
的内容添加样式,我们需要从不同的角度思考,借用CSS级联和继承的优势。
级联样式
因为SVG元素可以使用CSS通过三种不同的方法之一进行添加样式:外部的CSS样式(在外部的CSS文件中),内部样式块(<style>
元素包裹),以及内联样式(在元素的style
属性中)。重点在于这些级联管理是如何将样式应用到元素之上的。
除了CSS属性,SVG元素还可以使用描述属性添加样式。描述属性是在元素上设置CSS属性的简写方式。可以认为它们是特殊的样式属性。它们的目的就是给样式级联做贡献,但是可能正走在一个我们不太期望的方向上。
在下面的代码片段中,简单地展示了一个粉色的带黄色描边的圆。stroke
,stroke-width
和fill
都是描述属性。
<svg viewBox="0 0 100 100"><circle fill="deepPink" stroke="yellow" stroke-width="5" cx="50" cy="50" r="10"></circle></svg>
在SVG中,所有CSS属性的子集可以通过SVG属性设置,反之亦然。这意味着,不是所有的CSS属性都可以被指定给SVG元素作为描述属性,也不是所有SVG支持的描述属性都可以在CSS中指定,虽然有很多都可以。
SVG规范列出了可以设置为CSS属性的SVG属性。其中一些属性可以和CSS共享(也就是已经可以作为CSS属性),如opacity
和transform
,有一些还不行,如fill
、stroke
和stroke-width
。
在SVG 2中,这个列表将包括x
、y
、width
、height
、cx
、cy
,以及一些其它的描述属性,目前还不能在SVG 1.1中通过CSS来设置的。新的属性列表可以在SVG 2规范中找到。
如果你和我一样,那么你一定会期待描述属性可以有相比其它样式声明更高的特殊性。我的意思是,毕竟,外部的样式可以被内部的样式块覆盖,内部的样式块又可以被style
属性设置的内联样式覆盖。那么这看起来是不是越“内层”的样式,优先级就越高。所以如果一个属性有了自己的特性,它是不是就更强大,因此它也就可以覆盖所有其它的样式声明。尽管这对我来说是非常棒的,但是它真正的工作原理却不是这样的。
事实上,描述属性算是比较低层级的“作者样式层叠表”,可以被其它所有的样式定义覆盖:外部的样式表,内部的样式块以及内联样式。描述属性唯一超过的就是继承样式。就是说,描述属性只可以覆盖文档中的继承样式,但是会被其它所有的样式声明覆盖(:зゝ∠)。
好滴~既然我们现在弄清楚了,我们回到<use>
元素以及它的内容上吧。
我们现在知道我们不同使用CSS选择器给<use>
中的元素设置样式。
我们知道,正如<g>
元素,你应用给<use>
的样式将会被它所有的后代内容继承(也就是shadow DOM中的内容)。
所以第一个改变<use>
内元素的fill
颜色的尝试就是给<use>
元素本身应用此填充颜色,并让其继承和级联。
但是,这带来了两个问题:
该填充颜色将被
<use>
的所有后代内容继承,甚至包括那些你并不想给它们应用样式的内容(如果你的<use>
中还没有任何元素,那么这就不成问题。)如果是通过图形编辑器导出,或者是从其它设计师手中拿到的SVG,简单来说,就是你不能接触到SVG源码,那么你可能就没办法给SVG元素应用描述属性了(除非你明确指出你不希望在输出SVG的时候发生这个事情,但这是另一个话题了),这些属性的值将覆盖你给
<use>
应用的所有样式。现在,我假设如果你给<use>
指定了样式,而且希望这些样式可以被它的后代继承,那么描述属性可能会给你带来不便。
即使你可以获取SVG的代码,你也可以摆脱描述属性,我强烈建议不要这样做,因为:
删除那些用于设置某些属性的特性(:зゝ∠),将会导致这些属性被重置为初始的浏览器默认值——也就是,一般情况下,所有都是黑色填充和描边(比如我们现在讨论的是颜色)。
通过重置所有值,你可以强迫自己去给所有属性集指定样式,所以除非你想这么做,否则你不要想摆脱这些描述属性了。
描述属性设计的初衷是作为一项降级机制,用于当你的外部样式因为某些原因不能应用的时候。如果CSS因为某些东西给搞砸而不能加载的时候,你的图标至少可以有些默认的相对漂亮的样式可以降级。这点我强烈建议保留它们。
好了,现在我们有这些属性了,但是我们还想针对不同的实例应用不同的样式,比如说,不同的图标。
需要做的就是确保我们强制描述属性继承了设置于<use>
之上的样式,或者找到一个方法来让它们覆盖这些值。为了做到这一点,我们需要利用CSS的优势。
我们从最简单的实例开始,然后慢慢进入到更复杂的情景。
CSS描述属性值的介绍
描述属性可以被其它任何的样式声明覆盖。我们可以利用这个优势,用一个外部的样式声明,强制描述属性覆盖从use
继承的值。
通过使用CSSinherit
关键字,这会变得非常简单。看看下面的例子,我们绘制了一个冰淇淋的图标,只用一条路径完成,而且可以根据不同的情况改变填充颜色。这个图标是Erin Agnoli在Noun项目中创建的。
<svg><symbol id="ic"><path fill="#000" d="M81,40.933c0-4.25-3-7.811-6.996-8.673c-0.922-5.312-3.588-10.178-7.623-13.844 c-2.459-2.239-5.326-3.913-8.408-4.981c-0.797-3.676-4.066-6.437-7.979-6.437c-3.908,0-7.184,2.764-7.979,6.442 c-3.078,1.065-5.939,2.741-8.396,4.977c-4.035,3.666-6.701,8.531-7.623,13.844C22.002,33.123,19,36.682,19,40.933 c0,2.617,1.145,4.965,2.957,6.589c0.047,0.195,0.119,0.389,0.225,0.568l26.004,43.873c0.383,0.646,1.072,1.04,1.824,1.04 c0.748,0,1.439-0.395,1.824-1.04L77.82,48.089c0.105-0.179,0.178-0.373,0.225-0.568C79.855,45.897,81,43.549,81,40.933z M49.994,11.235c2.164,0,3.928,1.762,3.928,3.93c0,2.165-1.764,3.929-3.928,3.929s-3.928-1.764-3.928-3.929 C46.066,12.997,47.83,11.235,49.994,11.235z M27.842,36.301c0.014,0,0.027,0,0.031,0c1.086,0,1.998-0.817,2.115-1.907 c0.762-7.592,5.641-13.791,12.303-16.535c1.119,3.184,4.146,5.475,7.703,5.475c3.561,0,6.588-2.293,7.707-5.48 c6.664,2.742,11.547,8.944,12.312,16.54c0.115,1.092,1.037,1.929,2.143,1.907c2.541,0.013,4.604,2.087,4.604,4.631 c0,1.684-0.914,3.148-2.266,3.958H25.508c-1.354-0.809-2.268-2.273-2.268-3.958C23.24,38.389,25.303,36.316,27.842,36.301z M50.01,86.723L27.73,49.13h44.541L50.01,86.723z"/></symbol></svg>
这个冰淇淋图标的内容(也就是path
)是定义在一个<symbol>
元素中的,也就是说它们不会直接在SVG画布中渲染。
然后,我们使用<use>
渲染出多个图标实例。
<svg class="icon" viewBox="0 0 100 125"> <use class="ic-1" xlink:href="#ic" x="0" y="0" /></svg><svg class="icon" viewBox="0 0 100 125"> <use class="ic-2" xlink:href="#ic" x="0" y="0" /></svg>
我们在CSS中设置图标的宽度和高度。我使用了viewBox
一样的尺寸,但它们也不是一定要相同的。但是,为了避免SVG内多余的空白太多,保持它们的宽高比。
.icon { width: 100px; height: 125px;}
使用上面的代码,你可以得到下面的结果:
注意我给SVG添加了一个黑色的边框,这样大家才可以看到每个图的边界,我们定义的第一个SVG图标的内容并没有渲染。这里可以提出一点:你在symbol
中定义的SVG文档也会在页面中渲染出来,即使它没有包括渲染图形。为了避免这一点,确保你在第一个SVG中设置了display: none
。如果你没有隐藏包含图标定义的SVG,即使你没有明确设置任何尺寸,它也会被渲染出来——浏览器默认尺寸是300x150px
,这是在CSS中没有替代元素时的默认尺寸,所以你会在页面上得到一块白色的区域,尽管你并不想要这块东西。
现在让我们试试改变每个图标实例的填充颜色:
use.ic-1 { fill: skyblue;}use.ic-2 { fill: #FDC646;}
即使这样写了,图标的填充颜色还是不会有任何改变,因为继承的颜色值被path
元素的fill="#000"
覆盖了。为了阻止这个东西,我们强制让path
继承颜色值:
svg path { fill: inherit;}
瞧!我们给<use>
元素设置的颜色现在可以逐个应用于path
了。查看下面的demo,可以照自己喜欢的去改变颜色值,创建更多实例:
现在这种技术已经非常好用,当你想要强制<use>
的内容继承你设置的样式时。但是在大多数情况下,这可能不是你想要的。还有很多其它添加样式的场景,所以我们接下来会介绍一些其它的方法。
使用CSS的all
属性给<use>
的内容添加样式
前段时间我使用一个引用自use
的图标,我想让它里面的其中一个元素可以继承所有我给<use>
设置的样式,像fill
、stroke
、stroke-width
、opacity
甚至transform
。基本上,我希望可以控制所有这些CSS属性,同时保留标签中的描述属性作为降级。
如果你发现你也处在这样一个场景中,你可能会发现这用CSS做起来非常耗时间:
path#myPath { fill: inherit; stroke: inherit; stroke-width: inherit; transform: inherit; /* ... */}
看看上面的代码片段,你可以看到都是同一个模式,我们应该可以把所有这些属性结合起来,放到一个属性中,并把所有这些属性的值设置为inherit
。
幸运的是,这就是CSS的all
属性发光发热的时候了。我之前写过关于使用all
属性来给SVG的<use>
内容添加样式的参考条目,但是因为我们现在的上下文环境,我们需要再看看。
使用all
属性,我们可以这样写:
path#myPath { all: inherit;}
这在所有支持all
属性的浏览器中都工作得非常好(详细信息请查看属性参考条目),然而还有几个重点要记住:这条声明会真正地给元素的所有属性都设置从父元素继承值,包括那些你可能不想要的属性。所以除非你想要在CSS中给元素的所有属性都设置样式,否则你就不要使用它——这是一种极端的措施,当你想要暴露你的元素,然后在CSS中对它的样式属性进行完全的控制的时候才使用,这种情况比较少见。如果你使用这条声明,不在CSS中指定所有属性的值,它们就会直接往上然后级联,知道它们找到可以继承的值,大多数情况下就是浏览器的默认样式,从默认用户代理样式表加载而来。
注意这只会影响到那些可以在CSS中设置的属性,不包括那些SVG独有的属性。所以如果一个属性可以作为CSS属性设置,它就会被设置为inherit
,否则就不会。
能够强制描述属性去从<use>
继承样式是强大的,但是如果你的图标是由多个元素组成的呢,你肯定不想要让所有的这些元素都从use
继承同一个fill
颜色吧?那如果你想要给不同的use
级联应用多个填充颜色怎么办呢?给use
设置一个样式已经不足够了,我们需要一些其它的东西来帮助我们从正确的元素级联正确的颜色。
使用CSS的currentColor
变量来给<use>
内容添加样式
使用CSS的currentColor
变量,并结合上面的技术,我们可以给一个元素指定两种不同的颜色,而不仅是一种。Fabrice Weinberg在他的CodePen blog写了一些关于这种技术的文章。
有关于
currentColor
更多信息,可以参阅早前的一篇译文《使用CSS的currentColor变量扩展颜色级联》。
这种技术的内幕其实是在<use>
上同时使用fill
和color
属性,然后利用currentColor
的变量特性,让这些颜色级联到<use>
的内容上。我们先看一个代码实例,看看这是怎么搞的先。
假设我们要给这个小小的Codrops的logo添加两种颜色的样式——一个用于前面的水滴,一个用于后面的——logo的每一个实例都是采用两种颜色。
首先,我们从上面的代码截图开始:用symbol
包裹我们的图标定义,然后使用三个<use>
创建三个logo实例。
<svg style="display: none;"><symbol id="codrops" viewBox="0 0 23 30"><path class="back" fill="#aaa" d="M22.63,18.261c-0.398-3.044-2.608-6.61-4.072-9.359c-1.74-3.271-3.492-5.994-5.089-8.62l0,0 c-1.599,2.623-3.75,6.117-5.487,9.385c0.391,0.718,0.495,1.011,0.889,1.816c0.143,0.294,0.535,1.111,0.696,1.43 c0.062-0.114,0.582-1.052,0.643-1.162c0.278-0.506,0.54-0.981,0.791-1.451c0.823-1.547,1.649-2.971,2.469-4.33 c0.817,1.359,1.646,2.783,2.468,4.33c0.249,0.47,0.513,0.946,0.791,1.453c1.203,2.187,2.698,4.906,2.96,6.895 c0.292,2.237-0.259,4.312-1.556,5.839c-1.171,1.376-2.824,2.179-4.663,2.263c-1.841-0.084-3.493-0.887-4.665-2.263 c-0.16-0.192-0.311-0.391-0.448-0.599c-0.543,0.221-1.127,0.346-1.735,0.365c-0.56-0.019-1.095-0.127-1.599-0.313 c1.448,3.406,4.667,5.66,8.447,5.78C19.086,29.537,23.469,24.645,22.63,18.261z"/><path class="front" fill="#ddd" d="M6.177,11.659c0.212,0.367,0.424,0.747,0.635,1.136c0.164,0.303,0.333,0.606,0.512,0.927 c0.683,1.225,1.618,2.898,1.755,3.937c0.144,1.073-0.111,2.056-0.716,2.769c-0.543,0.641-1.315,1.014-2.186,1.067 c-0.87-0.054-1.643-0.43-2.186-1.067c-0.604-0.713-0.858-1.695-0.715-2.771c0.137-1.036,1.072-2.712,1.755-3.936 c0.18-0.32,0.349-0.623,0.513-0.927C5.752,12.404,5.964,12.026,6.177,11.659 M6.177,5.966L6.177,5.966 c-1.02,1.649-2.138,3.363-3.247,5.419c-0.932,1.728-2.344,3.967-2.598,5.88c-0.535,4.014,2.261,7.09,5.846,7.203 c3.583-0.113,6.379-3.189,5.845-7.203c-0.255-1.912-1.666-4.152-2.598-5.88C8.314,9.329,7.196,7.617,6.177,5.966L6.177,5.966z"/></symbol></svg><svg height="90px" width="69px"><use xlink:href="#codrops" class="codrops-1"/></svg><svg height="90px" width="69px"><use xlink:href="#codrops" class="codrops-2"/></svg><svg height="90px" width="69px"><use xlink:href="#codrops" class="codrops-3"/></svg>
如果我们想要给<use>
元素的每个实例设置fill
颜色,该颜色将会两条路径都继承,最后它们就会有相同的颜色——这不是我们想要的。
所以我们不仅要指定fill
颜色,还要让它按照默认方法级联,然后使用currentColor
变量来确保icon前面的小水滴获取和背景不同的颜色值:该值通过color
属性指定。
首先,我们需要在我们想要应用该颜色值的地方插入currentColor
;进入定义图标内容的标签,在<symbol>
中。代码如下:
<svg style="display: none;"><symbol id="codrops" viewBox="0 0 23 30"><path class="back" fill="#aaa" d="..."/><path class="front" fill="currentColor" d="..."/></symbol></svg>
下一步我们需要从另一个水滴中删除fill
描述属性,并让它从use
继承fill
颜色,而不是使用inherit
技术。
如果我们要使用inherit
关键字来强制描述属性从use
继承值,两条路径都会继承相同的值,这样currentColor
就不会再产生任何效果。所以,在这种技术中,我们需要删除我们想要在CSS中设置的属性,只保留那个我们想要使用currentColor
设置的值。
<svg style="display: none;"><symbol id="codrops" viewBox="0 0 23 30"><path class="back" d="..."/><path class="front" fill="currentColor" d="..."/></symbol></svg>
现在,在<use>
上使用fill
和color
属性,给水滴添加样式:
.codrops-1 { fill: #4BC0A5; color: #A4DFD1;}.codrops-2 { fill: #0099CC; color: #7FCBE5;}.codrops-3 { fill: #5F5EC0; color: #AEAFDD;}
每个<use>
元素有自己的fill
和color
值。对单个水滴来说,fill
颜色级联并被没有fill
属性的第一条路径(后面的水滴)继承,color
属性的值被作为第二条路径(前面的水滴)的fill
属性的值。
所以当前颜色值被引用到<use>
元素里边,使用currentColor
。漂亮整洁,对吗?
这是上面代码的demo:
这种双色变量技术对于简单的双色标志非常好用。在Fabrice的文章中,他创建了三个不同的sass logo,通过改变文本的颜色和背景颜色。
currentColor
关键字目前只在CSS变量中支持。但是,如果我们有更多的变量,是不是就可以分配和释放更多的值到<use>
内容中呢?对的!Amelia Bellamy-Royds在CodePen blog的文章中介绍了这个概念,大概一年多之前。我们来看看它是如何工作的。
将来:使用CSS自定义属性给<use>
内容添加样式,即CSS变量
使用CSS自定义属性(即CSS变量),你可以给<use>
的内容添加样式,而不需要强制浏览器覆盖任何描述属性的值。
如MDN中定义的,CSS变量可以是作者、或用户,定义的,web页面中包含整个文档中指定的值的实体。它们被设置了使用自定义属性,并通过一个指定的功能符号var()
访问。和CSS预处理器(如sass)的变量非常相似,但是更灵活,可以做预处理器变量不能做的事情。(CSS变量很快将被添加到Codrops CSS参考条目中,敬请期待。)
变量,即CSS变量或预处理器变量,都可以有很多使用示例,但是主题(颜色)是最常见的用例之一。在这一节中我们将讲解在给SVG添加样式时如何使用它。
我们先从一张symbol
中定义并通过use
实例化的图像开始,并且只为这张图像应用这种技术;只要你想,这个示例中给<use>
内容应用样式的概念,也可以被应用到很多<use>
元素中。
现在,假设我们有如下这个可爱时髦的机器人插画,Freepik设计~
机器人的代码包括了这些组成颜色。
<svg style="display: none"><symbol id="robot" viewBox="0 0 340 536"><path d="..." fill="#fff" /><path d="..." fill="#D1312C" /><path d="..." fill="#1E8F90" /><path d="..." fill="#1E8F90" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" /><path d="..." fill="#1E8F90" /><path d="..." fill="#6A4933" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" /><path d="..." fill="#F2B42B" /><path d="..." fill="#fff" /><!-- rest of the shapes --></symbol></svg>
现在,我们不打算使用CSS变量作为每条路径的fill
属性的值;我们将使用CSSfill
属性作为其填充颜色值,并保留原位置的fill
属性。这个属性将作为降级使用,在不支持CSS变量的浏览器中,这样这个图像在那些不支持变量的浏览器中,仍然能保留初始样式。
添加了变量之后,上面的代码变成如下:
<svg style="display: none"><symbol id="robot" viewBox="0 0 340 536"><path d="..." fill="#fff" /><path d="..." fill="#D1312C" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color)" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" /><path d="..." fill="#F2B42B" style="fill: var(--secondary-color)" /><path d="..." fill="#fff" /><!-- rest of the shapes --></symbol></svg>
因为内联style
标签会覆盖描述属性,支持CSS变量的浏览器会使用这些变量作为图形的填充颜色。不支持CSS变量的浏览器将使用fill
属性值。
下一步,我们需要在CSS中定义变量的值。首先,插画需要使用use
实例化:
<svg width="340" height="536"><use xlink:href="#robot" id="robot-1" /></svg>
然后,变量将会定义在use
上,这样它们会被级联到内容上。你给变量选的颜色将会构成你的插画内容的颜色主题。所以,对于上面的机器人,构成图形有三种主要的颜色,我把它们命名为primary、secondary和tertiary。
#robot-1 { --primary-color: #0099CC; --secondary-color: #FFDF34; --tertiary-color: #333;}
有这些变量,你仍然可以使用fill
和color
属性,但是你可能不需要或者根本不想要。所以,有了以上通过变量定义的颜色,我们的机器人如下:
你可以根据自己需要创建很多图像的副本,每个都定义一组不同的颜色,然后使用不同的颜色主题。当你想要给同一个logo根据上下文,以不同的方式为其添加样式时,或者任何其它相似的用例。
现在,我们提到那些不支持CSS变量的浏览器会降级到描述属性的初始样式,而不支持变量的浏览器将会使用fill
属性来覆盖属性。ok!但如果浏览器不支持CSS变量,而且作者还没有为它们提供指定的变量值,或者他们提供的值是无效的,会发生什么呢?
对于我们这里的时髦可爱的机器人,我们定义了三个变量,只有图像中的一小部分元素没有获取任何变量,因为它们使用默认的颜色,任何颜色的主题都是非常漂亮的。所以,如果你在支持CSS变量的浏览器(目前只有Firefox)中展示上面的代码,然后从CSS中删除变量声明,你将会得到:
如果如果没有设置变量值或者变量值无效,浏览器会使用默认颜色,通常是黑色的填充和描边。
避免的方法是为浏览器支持提供另一种降级颜色。事实上,CSS变量的语法就可以做到这一点:在var()
函数内部不要只提供变量值一条声明,你可以用逗号分隔提供两条声明:变量名和一个降级颜色值——在这里就是指我们在描述属性中使用的值。
所以,回到上面的机器人代码,如下:
<svg style="display: none"><symbol id="robot" viewBox="0 0 340 536"><path d="..." fill="#fff" /><path d="..." fill="#D1312C" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /><path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /><path d="..." fill="#fff" /><path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" /><path d="..." fill="#F2B42B" style="fill: var(--secondary-color, #F2B42B)" /><path d="..." fill="#fff" /><!-- rest of the shapes --></symbol></svg>
就是它了。对任何无法加载或没有定义值的变量,浏览器将会降级使用标签中定义的初始颜色。非常棒!
使用这种技术,你现在可以在页面上任何你想要的地方,使用<use>
引用机器人。在CSS中为每一个新实例定义一组变量值,这样每个实例都会有一组不同的颜色主题。
你可以看看上面的demo,创建很多机器人副本,然后指定不同的变量值,只要确保你使用的是Firefox浏览器,因为在写这篇文章的当前(2015.7.16)只有它支持CSS变量:
如果你是在Firefox中查看demo,你会看到我们用CSS变量定义的蓝色+黄色版本的机器人。记得在Chrome中查看其降级机制(绿色版本),然后在Firefox中尝试删除变量声明,看看降级如何。
总结
呼~写了这么长。
利用CSS样式的优势,给<use>
的内容添加样式——虽然在shadow DOM中可以不要这么复杂。用CSS变量(仅一个currentColor
或自定义属性)我们可以进入到shadow DOM中,定制我们的图形,并提供非常好的降级机制。
就我个人而言,我非常喜欢捣鼓CSS变量+SVG的组合。我喜欢他们组合在一起时非常地强大,特别是考虑到它们强大的降级机制。它们目前只在Firefox中支持,正如我们提到的,但是如果你想要看它们得到更多的支持,你可以在MS Edge User Voice forums上为它们投票。
在将来我们可能会有其它的方法来给use
内容添加样式,因为已经有很多这方面的讨论,关于使用CSS变量作为SVG的参数;所以这篇文章,虽然很长,而且可能没有涵盖到所有这方面的内容。如果你有什么其它的想法,请在评论中留言。
处理重用SVG元素的内容已经成为最热门的SVG话题之一,很多人都觉得很难处理,因为克隆的代码在对应的位置如何自然展示。涉及的和相关的话题非常多,但是这是其它文章要讨论的了。