用CSS给SVG 的内容添加样式

用CSS给SVG <use>的内容添加样式_第1张图片

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属性,以及用来给该元素定位的xy属性。你可以给use元素应用样式,这些样式也会级联应用到use元素的内容中去。

的内容是什么呢?它被克隆到哪里了?CSS级联如何处理这些内容?

在我们回答这些问题之前,因为我们目前只讲了SVG结构化和分组元素,这里还有几篇值得我们继续深入学习的文章,关于viewBox属性和的使用:

  • Structuring, Grouping, and Referencing in SVG — The  and  Elements
  • 理解SVG坐标系和变换:视窗,viewBox和preserveAspectRatio

SVG 及shadow DOM

当你使用引用元素时,代码如下:

<symbol id="my-icon" viewBox="0 0 30 30">
    
symbol>

<use xlink:href="#my-icon" x="100" y="300" />

渲染在屏幕上的东西是内容定义在内的图标,但是这不是真正渲染出的内容,而是的内容,也就是内容的一个副本或者克隆。

但是元素只是一个元素,它是自闭合的。在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”,而且当点开此片段的内容时——会发现它就是内容的副本。

用CSS给SVG <use>的内容添加样式_第2张图片

检查一下这些代码,你可以看到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的交互时,我把它当成普通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文件中),内部样式块(