Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVG样式初探 #5

Open
OndayX opened this issue Apr 25, 2021 · 0 comments
Open

SVG样式初探 #5

OndayX opened this issue Apr 25, 2021 · 0 comments

Comments

@OndayX
Copy link
Owner

OndayX commented Apr 25, 2021

除了默认常见的内联样式外,SVG中还有多种方式添加样式。

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 , , and Elements
理解SVG坐标系和变换:视窗,viewBox和preserveAspectRatio
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文件中),内部样式块(<style>元素包裹),以及内联样式(在元素的style属性中)。重点在于这些级联管理是如何将样式应用到元素之上的。

除了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,简单来说,就是你不能接触到SVG源码,那么你可能就没办法给SVG元素应用描述属性了(除非你明确指出你不希望在输出SVG的时候发生这个事情,但这是另一个话题了),这些属性的值将覆盖你给应用的所有样式。现在,我假设如果你给指定了样式,而且希望这些样式可以被它的后代继承,那么描述属性可能会给你带来不便。
即使你可以获取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设计~

机器人插画

机器人的代码包括了这些组成颜色。

   <!-- rest of the shapes -->
</symbol>
现在,我们不打算使用CSS变量作为每条路径的fill属性的值;我们将使用CSSfill属性作为其填充颜色值,并保留原位置的fill属性。这个属性将作为降级使用,在不支持CSS变量的浏览器中,这样这个图像在那些不支持变量的浏览器中,仍然能保留初始样式。

添加了变量之后,上面的代码变成如下:

    <!-- rest of the shapes -->
</symbol>
因为内联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()函数内部不要只提供变量值一条声明,你可以用逗号分隔提供两条声明:变量名和一个降级颜色值——在这里就是指我们在描述属性中使用的值。

所以,回到上面的机器人代码,如下:

     <!-- rest of the shapes -->
</symbol>
就是它了。对任何无法加载或没有定义值的变量,浏览器将会降级使用标签中定义的初始颜色。非常棒!

使用这种技术,你现在可以在页面上任何你想要的地方,使用引用机器人。在CSS中为每一个新实例定义一组变量值,这样每个实例都会有一组不同的颜色主题。

你可以看看上面的demo,创建很多机器人副本,然后指定不同的变量值,只要确保你使用的是Firefox浏览器,因为在写这篇文章的当前(2015.7.16)只有它支持CSS变量:

如果你是在Firefox中查看demo,你会看到我们用CSS变量定义的蓝色+黄色版本的机器人。记得在Chrome中查看其降级机制(绿色版本),然后在Firefox中尝试删除变量声明,看看降级如何。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: https://www.w3cplus.com/svg/styling-svg-use-content-css.html © w3cplus.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant