转自:http://www.w3cplus.com/svg/styling-svg-use-content-css.html
一篇深入探究如何给SVG
svg use
SVG图形的一个最常见用例是图标系统,其中最常用的SVG sprite技术就是使用SVG 元素在文档中任意位置“实例化”图标。
使用 元素实例化图标或任何其它的SVG元素或图像,给元素添加样式时经常会碰到一些问题。这篇文章的目的是尽可能给你介绍一些方法来解决:使用 引入的内容添加样式受限的问题。
但是在开始之前,我们先快速浏览一下SVG的主要结构和分组元素,然后慢慢进入use的世界中,以及shadow DOM,然后重回CSS的怀抱。我们会逐步讲解为什么给 内容添加样式会比较麻烦,以及有什么好的解决方案。
SVG结构化、分组,以及在SVG中引用(重用)元素速览
SVG中有四个主要的元素用于在文档中定义、结构化和引用SVG代码。这些元素使得重用SVG元素变得容易,同时保持代码的简洁性和可读性。因为SVG的特性,这些元素和图形编辑器中的某些命令具有相同的功能。
这四个用于SVG分组和引用的主要元素是: , , 和 。
元素(“group”的简写),用于给逻辑上相联系的图形元素分组。从图形编辑器的角度,如Adobe Illustrator, 元素提供了类似于分组对象的功能。你也可以把它想象成图形编辑器中图层的概念,因为一层也是一组元素。
当你想要应用某个样式,并希望这个样式能被组中的所有元素继承,分组元素 非常好用,特别是当你想要给某组元素应用动画,同时还需要保持它们彼此的空间关系的时候。
元素用来定义你之后要重用的元素。当你想要创建某一类在文档中要多次使用的“模板”时,使用 定义元素。在 元件中定义的元素不会在画布中渲染出来,除非你在文档的某个位置调用了它们。
可以用于定义很多东西,但是最主要的使用情景之一是定义类似渐变的图案,例如,使用这些渐变作为其它SVG元素的描边填充。它可以用来定义你想要在画布上渲染的任何元素。
元素结合了 和 元素的优点,将定义模板的元素组合在一起,以便之后在文档中的其他位置引用。和 不同,通常不用于定义图案,但是经常用于定义例如图标这样的标志,在整个文档中都可以被引用。
元素相比其它两个元素有一个非常重要的优点:它接受一个viewBox属性,可以让它在任何视窗中自适应大小缩放渲染。
元素用于引用文档中其它位置定义的元素。你可以重用已有的元素,类似于图形编辑器中的复制粘贴功能。它可以重用单个元素,也可以重用一组用 、 或定义的元素。
要使用一个元素,你需要通过一个标识对该元素进行引用——一个ID,即use中的xlink:href属性,以及用来给该元素定位的x和y属性。你可以给use元素应用样式,这些样式也会级联应用到use元素的内容中去。
的内容是什么呢?它被克隆到哪里了?CSS级联如何处理这些内容?
在我们回答这些问题之前,因为我们目前只讲了SVG结构化和分组元素,这里还有几篇值得我们继续深入学习的文章,关于viewBox属性和的使用:
Structuring, Grouping, and Referencing in SVG — The ,
SVG 及shadow DOM
当你使用 引用元素时,代码如下:
渲染在屏幕上的东西是内容定义在内的图标,但是这不是真正渲染出的内容,而是 的内容,也就是内容的一个副本或者克隆。
但是 元素只是一个元素,它是自闭合的。在use标签的开闭区间内没有任何内容,所以的内容是克隆到哪里了呢?
答案是:Shadow DOM。(不知道为什么,shadow DOM总是让我想起蝙蝠侠(:зゝ∠)。)
什么是shadow DOM?
shadow DOM和常规的DOM很类似,不同之处在于shadow DOM不是主文档子树的一员,shadow DOM中的结点属于文档片段,基本上等同于另一棵结点树,不能像普通结点那样添加脚本和样式。这给了作者们一种方法来封装和包裹样式及脚本,当创建模块化组件时。如果你使用过HTML5的video元素,或range input类型,也很好奇video控件或者范围输入组件是从哪里来的,那么你就已经接触过shadow DOM了。
在SVG 元素中,引用元素的内容被复制到一个文档片段中保存,这个文档片段是由 保留着。 在这里就是一个shadow Host。
所以, 的内容(克隆或复制那个它引用的元素的)都表示在一个shadow文档片段中。
也就是说,它们就在那里,但是并不可见。就像普通的DOM内容一样,但是并不是在“高等级”的DOM中,并不能在主文档中被CSS选择器和JavaScript选中,它们被复制到由 保留的文档片段中。
现在,如果你是一个设计师,你可能会想:“ok,我了解了这东西了,但是有什么方法可以检查子文档,来看看它的真正的内容呢?”答案是:有的!你可以使用Chrome的开发者工具预览shadow DOM的内容。(现在还无法在Firefox中查看shadow DOM的内容。)但是为了完成这个,你需要先在“General”面板中勾选shadow DOM检查的选项。也就是:打开 Chrome 的开发者工具,点击右上角的“Settings”按钮勾选“Show user agent shadow DOM”。
在开发者工具中勾选了shadow DOM检查这一项之后,你可以在Elements面板中看到克隆的元素,和普通的DOM元素一样。下面的图片展示了 元素引用元素的内容的示例。注意到有一个“#shadow-root”,而且当点开此片段的内容时——会发现它就是内容的副本。
shadow DOM
检查一下这些代码,你可以看到shadow DOM和普通的DOM非常相似,除了在主文档中用CSS和JavaScript处理时有不同的特性之外。它们之间还存在其它差异,但是这一节不可能完全在讲shadow DOM,因为这真的是一个很大的概念,所以如果你想要阅读和了解更多关于它的内容的话,我推荐下面这几篇文章:
Intro to Shadow DOM
What the Heck is Shadow DOM?
Shadow DOM 101
Introduction to Shadow DOM (Video)
图灵社区的同学翻译的shadow DOM系列文章。
对我来说,考虑如何限制和shadow DOM的交互时,我把它当成普通DOM一样,除了在用CSS(和JavaScript)添加样式时需要不同地处理。但是对于SVG开发者来说就是一个问题:shadow DOM中 的内容如何存在,当需要给内容应用样式或者改变样式的时候,因为我们希望可以为它们添加样式。使用 的目的是为了可以创建某个元素的多个不同的“副本”,很多情境下,我们想要的是可以给差异化地给不同的副本添加样式。例如,考虑一个有两种样式的logo(反转颜色的主题)或多种颜色的icon,每一个都有自己的主题。这时,我们自然而然就会想到使用CSS来完成。
也就是说,我们前面提到的shadow DOM的内容在CSS看来不能像普通DOM一样添加样式。所以,我们要怎么给它的内容添加样式呢?我们不能像这样指向 的路径级联:
use path#line {
stroke: #009966;
}
因为我们不能使用普通的CSS选择器来获取shadow DOM。
有一组特殊的选择器可以让我们打破普通DOM的界限,给它里面的结点应用样式,但是这些选择器并没有很好的浏览器支持,而且相比CSS中提供的一长串用来选中普通DOM元素的选择器,它们是受限的。
此外,我们希望有一个更简单的方式来给SVG 的内容添加样式,而不需要去接触shadow DOM的具体内容——只使用简单的CSS和SVG。
为了实现以及获得更多一点控制,给 的内容添加样式,我们需要从不同的角度思考,借用CSS级联和继承的优势。
级联样式
因为SVG元素可以使用CSS通过三种不同的方法之一进行添加样式:外部的CSS样式(在外部的CSS文件中),内部样式块(
除了CSS属性,SVG元素还可以使用描述属性添加样式。描述属性是在元素上设置CSS属性的简写方式。可以认为它们是特殊的样式属性。它们的目的就是给样式级联做贡献,但是可能正走在一个我们不太期望的方向上。
在下面的代码片段中,简单地展示了一个粉色的带黄色描边的圆。stroke,stroke-width和fill都是描述属性。
在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属性设置的内联样式覆盖。那么这看起来是不是越“内层”的样式,优先级就越高。所以如果一个属性有了自己的特性,它是不是就更强大,因此它也就可以覆盖所有其它的样式声明。尽管这对我来说是非常棒的,但是它真正的工作原理却不是这样的。
事实上,描述属性算是比较低层级的“作者样式层叠表”,可以被其它所有的样式定义覆盖:外部的样式表,内部的样式块以及内联样式。描述属性唯一超过的就是继承样式。就是说,描述属性只可以覆盖文档中的继承样式,但是会被其它所有的样式声明覆盖(:зゝ∠)。
好滴~既然我们现在弄清楚了,我们回到 元素以及它的内容上吧。
我们现在知道我们不同使用CSS选择器给 中的元素设置样式。
我们知道,正如 元素,你应用给 的样式将会被它所有的后代内容继承(也就是shadow DOM中的内容)。
所以第一个改变 内元素的fill颜色的尝试就是给 元素本身应用此填充颜色,并让其继承和级联。
但是,这带来了两个问题:
该填充颜色将被
即使你可以获取SVG的代码,你也可以摆脱描述属性,我强烈建议不要这样做,因为:
删除那些用于设置某些属性的特性(:зゝ∠),将会导致这些属性被重置为初始的浏览器默认值——也就是,一般情况下,所有都是黑色填充和描边(比如我们现在讨论的是颜色)。
通过重置所有值,你可以强迫自己去给所有属性集指定样式,所以除非你想这么做,否则你不要想摆脱这些描述属性了。
描述属性设计的初衷是作为一项降级机制,用于当你的外部样式因为某些原因不能应用的时候。如果CSS因为某些东西给搞砸而不能加载的时候,你的图标至少可以有些默认的相对漂亮的样式可以降级。这点我强烈建议保留它们。
好了,现在我们有这些属性了,但是我们还想针对不同的实例应用不同的样式,比如说,不同的图标。
需要做的就是确保我们强制描述属性继承了设置于 之上的样式,或者找到一个方法来让它们覆盖这些值。为了做到这一点,我们需要利用CSS的优势。
我们从最简单的实例开始,然后慢慢进入到更复杂的情景。
CSS描述属性值的介绍
描述属性可以被其它任何的样式声明覆盖。我们可以利用这个优势,用一个外部的样式声明,强制描述属性覆盖从use继承的值。
通过使用CSSinherit关键字,这会变得非常简单。看看下面的例子,我们绘制了一个冰淇淋的图标,只用一条路径完成,而且可以根据不同的情况改变填充颜色。这个图标是Erin Agnoli在Noun项目中创建的。
这个冰淇淋图标的内容(也就是path)是定义在一个元素中的,也就是说它们不会直接在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;
}
瞧!我们给 元素设置的颜色现在可以逐个应用于path了。查看下面的demo,可以照自己喜欢的去改变颜色值,创建更多实例:
现在这种技术已经非常好用,当你想要强制 的内容继承你设置的样式时。但是在大多数情况下,这可能不是你想要的。还有很多其它添加样式的场景,所以我们接下来会介绍一些其它的方法。
使用CSS的all属性给 的内容添加样式
前段时间我使用一个引用自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的 内容添加样式的参考条目,但是因为我们现在的上下文环境,我们需要再看看。
使用all属性,我们可以这样写:
path#myPath {
all: inherit;
}
这在所有支持all属性的浏览器中都工作得非常好(详细信息请查看属性参考条目),然而还有几个重点要记住:这条声明会真正地给元素的所有属性都设置从父元素继承值,包括那些你可能不想要的属性。所以除非你想要在CSS中给元素的所有属性都设置样式,否则你就不要使用它——这是一种极端的措施,当你想要暴露你的元素,然后在CSS中对它的样式属性进行完全的控制的时候才使用,这种情况比较少见。如果你使用这条声明,不在CSS中指定所有属性的值,它们就会直接往上然后级联,知道它们找到可以继承的值,大多数情况下就是浏览器的默认样式,从默认用户代理样式表加载而来。
注意这只会影响到那些可以在CSS中设置的属性,不包括那些SVG独有的属性。所以如果一个属性可以作为CSS属性设置,它就会被设置为inherit,否则就不会。
能够强制描述属性去从 继承样式是强大的,但是如果你的图标是由多个元素组成的呢,你肯定不想要让所有的这些元素都从use继承同一个fill颜色吧?那如果你想要给不同的use级联应用多个填充颜色怎么办呢?给use设置一个样式已经不足够了,我们需要一些其它的东西来帮助我们从正确的元素级联正确的颜色。
使用CSS的currentColor变量来给 内容添加样式
使用CSS的currentColor变量,并结合上面的技术,我们可以给一个元素指定两种不同的颜色,而不仅是一种。Fabrice Weinberg在他的CodePen blog写了一些关于这种技术的文章。
有关于currentColor更多信息,可以参阅早前的一篇译文《使用CSS的currentColor变量扩展颜色级联》。
这种技术的内幕其实是在 上同时使用fill和color属性,然后利用currentColor的变量特性,让这些颜色级联到 的内容上。我们先看一个代码实例,看看这是怎么搞的先。
假设我们要给这个小小的Codrops的logo添加两种颜色的样式——一个用于前面的水滴,一个用于后面的——logo的每一个实例都是采用两种颜色。
currentColor
首先,我们从上面的代码截图开始:用symbol包裹我们的图标定义,然后使用三个 创建三个logo实例。
如果我们想要给 元素的每个实例设置fill颜色,该颜色将会两条路径都继承,最后它们就会有相同的颜色——这不是我们想要的。
所以我们不仅要指定fill颜色,还要让它按照默认方法级联,然后使用currentColor变量来确保icon前面的小水滴获取和背景不同的颜色值:该值通过color属性指定。
首先,我们需要在我们想要应用该颜色值的地方插入currentColor;进入定义图标内容的标签,在中。代码如下:
下一步我们需要从另一个水滴中删除fill描述属性,并让它从use继承fill颜色,而不是使用inherit技术。
如果我们要使用inherit关键字来强制描述属性从use继承值,两条路径都会继承相同的值,这样currentColor就不会再产生任何效果。所以,在这种技术中,我们需要删除我们想要在CSS中设置的属性,只保留那个我们想要使用currentColor设置的值。
现在,在 上使用fill和color属性,给水滴添加样式:
.codrops-1 {
fill: #4BC0A5;
color: #A4DFD1;
}
.codrops-2 {
fill: #0099CC;
color: #7FCBE5;
}
.codrops-3 {
fill: #5F5EC0;
color: #AEAFDD;
}
每个 元素有自己的fill和color值。对单个水滴来说,fill颜色级联并被没有fill属性的第一条路径(后面的水滴)继承,color属性的值被作为第二条路径(前面的水滴)的fill属性的值。
所以当前颜色值被引用到 元素里边,使用currentColor。漂亮整洁,对吗?
这是上面代码的demo:
这种双色变量技术对于简单的双色标志非常好用。在Fabrice的文章中,他创建了三个不同的sass logo,通过改变文本的颜色和背景颜色。
currentColor关键字目前只在CSS变量中支持。但是,如果我们有更多的变量,是不是就可以分配和释放更多的值到 内容中呢?对的!Amelia Bellamy-Royds在CodePen blog的文章中介绍了这个概念,大概一年多之前。我们来看看它是如何工作的。
将来:使用CSS自定义属性给 内容添加样式,即CSS变量
使用CSS自定义属性(即CSS变量),你可以给 的内容添加样式,而不需要强制浏览器覆盖任何描述属性的值。
如MDN中定义的,CSS变量可以是作者、或用户,定义的,web页面中包含整个文档中指定的值的实体。它们被设置了使用自定义属性,并通过一个指定的功能符号var()访问。和CSS预处理器(如sass)的变量非常相似,但是更灵活,可以做预处理器变量不能做的事情。(CSS变量很快将被添加到Codrops CSS参考条目中,敬请期待。)
变量,即CSS变量或预处理器变量,都可以有很多使用示例,但是主题(颜色)是最常见的用例之一。在这一节中我们将讲解在给SVG添加样式时如何使用它。
我们先从一张symbol中定义并通过use实例化的图像开始,并且只为这张图像应用这种技术;只要你想,这个示例中给 内容应用样式的概念,也可以被应用到很多 元素中。
现在,假设我们有如下这个可爱时髦的机器人插画,Freepik设计~
机器人插画
机器人的代码包括了这些组成颜色。
现在,我们不打算使用CSS变量作为每条路径的fill属性的值;我们将使用CSSfill属性作为其填充颜色值,并保留原位置的fill属性。这个属性将作为降级使用,在不支持CSS变量的浏览器中,这样这个图像在那些不支持变量的浏览器中,仍然能保留初始样式。
添加了变量之后,上面的代码变成如下:
因为内联style标签会覆盖描述属性,支持CSS变量的浏览器会使用这些变量作为图形的填充颜色。不支持CSS变量的浏览器将使用fill属性值。
下一步,我们需要在CSS中定义变量的值。首先,插画需要使用use实例化:
然后,变量将会定义在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()函数内部不要只提供变量值一条声明,你可以用逗号分隔提供两条声明:变量名和一个降级颜色值——在这里就是指我们在描述属性中使用的值。
所以,回到上面的机器人代码,如下:
就是它了。对任何无法加载或没有定义值的变量,浏览器将会降级使用标签中定义的初始颜色。非常棒!
使用这种技术,你现在可以在页面上任何你想要的地方,使用 引用机器人。在CSS中为每一个新实例定义一组变量值,这样每个实例都会有一组不同的颜色主题。
你可以看看上面的demo,创建很多机器人副本,然后指定不同的变量值,只要确保你使用的是Firefox浏览器,因为在写这篇文章的当前(2015.7.16)只有它支持CSS变量:
如果你是在Firefox中查看demo,你会看到我们用CSS变量定义的蓝色+黄色版本的机器人。记得在Chrome中查看其降级机制(绿色版本),然后在Firefox中尝试删除变量声明,看看降级如何。
总结
呼~写了这么长。
利用CSS样式的优势,给 的内容添加样式——虽然在shadow DOM中可以不要这么复杂。用CSS变量(仅一个currentColor或自定义属性)我们可以进入到shadow DOM中,定制我们的图形,并提供非常好的降级机制。
就我个人而言,我非常喜欢捣鼓CSS变量+SVG的组合。我喜欢他们组合在一起时非常地强大,特别是考虑到它们强大的降级机制。它们目前只在Firefox中支持,正如我们提到的,但是如果你想要看它们得到更多的支持,你可以在MS Edge User Voice forums上为它们投票。
在将来我们可能会有其它的方法来给use内容添加样式,因为已经有很多这方面的讨论,关于使用CSS变量作为SVG的参数;所以这篇文章,虽然很长,而且可能没有涵盖到所有这方面的内容。如果你有什么其它的想法,请在评论中留言。
处理重用SVG元素的内容已经成为最热门的SVG话题之一,很多人都觉得很难处理,因为克隆的代码在对应的位置如何自然展示。涉及的和相关的话题非常多,但是这是其它文章要讨论的了。
希望你喜欢这篇文章,并能对你有所帮助,感谢阅读!
本文根据@SaraSoueidan的《Styling SVG Content with CSS》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://tympanus.net/codrops/2015/07/16/styling-svg-use-content-css。