β

SVG中的结构化、分组和引用元素

W3CPlus 322 阅读

SVG有自己结构化文档的方式。通过某些SVG元素,我们可以在文档中定义、分组以及引用对象。这些元素使得元件重用变得简单,而且还保持了代码的简洁性和可读性。在这篇文章中我们将讲解这些元素,并指出它们之间的区别,以及每个元素各自的优势。

使用 <g> 元素分组

<g> 中的 g 代表group(分组)的意思。分组元素用于在逻辑上对相关的图形元素进行分组。从图形编辑器的角度,例如Adobe Illustrator, <g> 元素提供了类似于Group Object的功能。你也可以认为分组和图形编辑器中图层的概念是相似的,因为一个图层就是一组元素。

<g> 元素将其所有子内容分到一组。它通常有一个 id 属性,用来给分组命名。你给 <g> 元素应用的样式也都会被应用于它所有的子元素。所以它很容易添加样式、动画、交互,甚至整个组的对象的动画。

例如,下面是一个SVG绘制的鸟的图。这只鸟是用几个简单的形状组合完成的,包括圆和路径。

Group

如果你想要在Illustrator中把整只鸟从一个位置移动到另一个位置,你需要将这些元素组合到一起,这样你才不需要在每次移动的时候都将它们一个个选中。

group

这和SVG中使用 <g> 元素分组的原理是相同的。在这个示例中,我们和身体部分的元素放一个组,头部的元素放一个组,然后将这两组内容再放到一个组中,赋予 id bird

<svg width="1144.12px" height="400px" viewBox="0 0 572.06 200">
    <style>
        svg{background-color:white;}
        #wing{fill:#81CCAA;}
        #body{fill:#B8E4C2;}
        #pupil{fill:#1F2600;}
        #beak{fill:#F69C0D;}
        .eye-ball{fill:#F6FDC4;}
    </style>
    <g id="bird">
        <g id="body">
            <path d="M48.42,78.11c0-17.45,14.14-31.58,31.59-31.58s31.59,14.14,31.59,31.58c0,17.44-14.14,31.59-31.59,31.59
            S48.42,95.56,48.42,78.11"/>
            <path d="M109.19,69.88c0,0-8.5-27.33-42.51-18.53c-34.02,8.81-20.65,91.11,45.25,84.73
            c40.39-3.65,48.59-24.6,48.59-24.6S124.68,106.02,109.19,69.88"/>
            <path id="wing" d="M105.78,75.09c4.56,0,8.84,1.13,12.62,3.11c0,0,0.01-0.01,0.01-0.01l36.23,12.38c0,0-13.78,30.81-41.96,38.09
            c-1.51,0.39-2.82,0.59-3.99,0.62c-0.96,0.1-1.92,0.16-2.9,0.16c-15.01,0-27.17-12.17-27.17-27.17
            C78.61,87.26,90.78,75.09,105.78,75.09"/>
        </g>
        <g id="head">
            <path id="beak" d="M50.43,68.52c0,0-8.81,2.58-10.93,4.86l9.12,9.87C48.61,83.24,48.76,74.28,50.43,68.52"/>
            <path class="eye-ball" d="M60.53,71.68c0-6.33,5.13-11.46,11.46-11.46c6.33,0,11.46,5.13,11.46,11.46c0,6.33-5.13,11.46-11.46,11.46
                C65.66,83.14,60.53,78.01,60.53,71.68"/>
            <path id="pupil" d="M64.45,71.68c0-4.16,3.38-7.53,7.54-7.53c4.16,0,7.53,3.37,7.53,7.53c0,4.16-3.37,7.53-7.53,7.53
                C67.82,79.22,64.45,75.84,64.45,71.68"/>
            <path class="eye-ball" d="M72.39,74.39c0-2.73,2.22-4.95,4.95-4.95c2.73,0,4.95,2.21,4.95,4.95c0,2.74-2.22,4.95-4.95,4.95
                C74.6,79.34,72.39,77.13,72.39,74.39"/>
        </g>
    </g>
</svg>

如果你想要改变 #body 分组的填充颜色,它里边的所有元素都会变成你指定的颜色,非常方便。

分组元素非常好用,不仅是因为其组织和结构的特性。当你想要给由几块内容组成的SVG图像添加交互或动画的时候,非常好用。你可以把这些内容项放到一个组中,然后给它们定义移动、缩放或旋转的动画,这样它们相互之间的空间关系就可以被保持,也就是位置不会被打乱。

如果你想要对整只鸟进行缩放,让它变成现在尺寸的两倍,只需要一行CSS即可完成。

#bird {
    transform: scale(2);
}

分组尤其使得交互变得非常方便。你可以将鼠标事件应用到整只鸟上,然后让它作为一整个组去回应事件,而不必给组中的每个元素分别去应用相同的交互或者变换。

<g> 元素有一个更重要的特性:它可以有自己的 <title> <desc> 标签,使其更容易被屏幕阅读器解读,而且代码整体的可读性也更好。例如:

<g id="bird">
    <title>Bird</title>
    <desc>An image of a cute little green bird with an orange beak.</desc>
    <!-- ... -->
</g>

使用 <use> 重用元素

很多时候,在一个图像中,有一些元素是重复使用的。在Illustrator中如果你想要重复某个元素,你需要复制该元素,然后把它粘贴到相应的位置。复制粘贴现有元素比重新创建一个相同的元素要方便得多。

<use> 元素可以让你重用现有的元素,给你一个类似于图形编辑器中复制粘贴的功能。它可以用于重用单个元素,也可以重用一组用 <g> 定义的元素。

<use> 元素有 x y height width 属性,它通过使用 xlink:href 属性引用其它内容。所以如果你已经定义了一个分组,并给它赋予了 id ,当你想要在其它地方使用它时,你只需要在 xlink:href 属性中给一个URI,然后指定 x y 的位置,也就是该组图像显示的原点 (0, 0)

例如,当我们想要在我们的SVG画布上创建另一只鸟时,代码如下:

<use x="100" y="100" xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#bird" />

你可以在 xlink:href 属性中引用任何SVG元素,即使该元素是存在于外部文件中的。引用的元素和组不需要一定存在于同一个文件中。这对于组织以及缓存文件来说是非常棒的(例如,你可以单独给要用于重用的元素建一个文件)。例如,如果我们示例中的鸟是在一个单独的叫做 animals.svg 的文件中创建的,我们可以像这样引用它:

<use x="100" y="100" xlink:href="http://www.w3cplus.com/svg/path/to/animals.svg#bird" />

但是,在 <use> 中引用外部SVG在大多数版本的IE中是不行的(至少要IE11)。我建议你阅读Chris Coyier的这篇 文章 了解详细的情况,以及降级机制。

现在,你可能已经注意到,我说 <use> 中的 x y 属性指定了分组元素开始的位置,也就是元素左上角应该处的位置。移动元素意味着你从当前位置开始,将其移动到另一个位置。我指的是“应该定位到”,它会暗示元素根据 use 中的坐标系统在整个画布上定位元素,对吧?

但是事实证明, x y 坐标系其实是使用变换属性平移元素的简写。更具体地说,上面的 <use> 等同于:

<use xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#bird" transform="translate(100, 100)" />

use

这个事实意味着我们现在的新的重用元素的位置,其实是相对于我们使用的原始元素的位置来定位的。这并不是什么好的特性,有一些缺点。

<use> 元素的另一个缺点是:初始元素的“副本”会和初始元素保持相同的样式。你给 #bird 组元素应用的任何样式或变换,鸟的副本也会拥有相同的样式和变换。

你可以 use 一个元素,并给它应用独立的变换,例如,下面的这行代码将会重用我们的这只鸟,然后使用了一个缩放变换,将元件变成初始大小的一半。

<use x="100" y="100" xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#bird" transform="scale(0.5)" />

但是,你不能在副本中覆盖初始元素的样式(例如描边和填充)。这也就意味着如果你想要创建多只鸟或多个图标,你可能希望每个图标都是不同的颜色,这是不可能用 <use> 元素完成的(除非初始元素是在 <defs> 元素中定义的,并且没有应用这些样式。详情请阅读下一节)。

<use> 元素可以让你重用一个已经在画布上渲染过的元素。但是如果你想要定义一个元素,但是并不想让它显示出来,等到想使用的时候再调用?这时候就需要 <defs> 元素了。

使用 <defs> 重用已存储元素

<defs> 元素可以用来存储那些我们不想直接显示的内容。换句话说, <defs> 元素就是用来定义元件,但是不直接渲染。这个隐藏的存储元件可以在后面被其它SVG元素应用及显示,这使得它非常适合用于绘制那些包含重用图像的图案。

所以,使用 <defs> 我们可以定义一个我们想要使用的元素。这个元素可以是任何内容,可以是我们前面看到的一只鸟,也可以是裁剪路径、蒙版或一个线性渐变。基本上,任何内容,只要是我们想要定义并保存,然后在后面再使用的,我们都可以在 <defs> 中定义,而且该元件可以保存为模板,或是作为一种工具,以便将来使用。模板仅在实例化的时候显示。

下面的示例定义了一个SVG渐变,然后把它作为一个简单的SVG矩形的填充颜色:

<svg>
    <defs>
        <linearGradient id="gradient">
            <stop offset="0%" style="stop-color: deepPink"></stop>
            <stop offset="100%" style="stop-color: #009966"></stop>
        </linearGradient>
    </defs>
    <rect stroke="#eee" stroke-width="5" fill="url(#gradient)"></rect>
</svg>

<defs> 元素中定义线性渐变,就是确保该渐变不会被渲染,除非它在哪个需要的地方被引用了。

在上一节中我们提到了 <use> 元素的两个缺陷:

  • 新元素的位置相对于初始元素定位。
  • 初始元素的样式不能在新副本中被覆盖。

的确,还包括重用 use 元素会在画布上渲染这一点。

使用 <defs> 元素,所有这些缺陷都可以避免。不仅不会渲染初始元素,而且当你想要重用 <defs> 中的元素时,你为每个示例指定的定位都是相对于用户坐标系统的原点,而不是相对于初始元素的位置(也就是初始元素是一个模板,甚至都不需要在画布上渲染出来)。

在这个示例中,我们有一棵树,这棵树由一个树干和一组树叶组成。树叶组成了一个组, id id="leaves" ,这个组又和树干组合成了一个更大的叫做 tree 的组。

<svg width="500.79px" height="200px" viewBox="0 0 500.79 200">
    <style type="text/css">
        #leaves{fill:#8CC63F;}
        #bark{fill:#A27729;}
    </style>
    <g id="tree">
        <path id="bark" d="M91.33,165.51c0,0,4.18-27.65,1.73-35.82l-18.55-25.03l3.01-2.74l17.45,19.87l1.91-37.6h4.44l1.83,24.53
        l15.26-16.35l3.27,4.36l-16.07,19.34c0,0-2.72,0-1.09,19.34c1.63,19.34,3,29.7,3,29.7L91.33,165.51z"/>
        <g id="leaves">
            <path class="leaf" d="M96.97,79.07c0,0-14.92,4.34-23.52-14.05c0,0,19.4-7.98,24.37,11.9c0,0-9.68-3.57-13.07-6.73
                C84.75,70.2,91.82,77.99,96.97,79.07z"/>
            <path class="leaf" d="M74.07,100.91c0,0-15.94-1.51-17.2-22.39c0,0,21.62-0.27,18.83,20.66c0,0-7.92-7.1-9.97-11.41
                C65.73,87.77,69.55,97.92,74.07,100.91z"/>
            <!-- ... -->
        </g>
    </g>
</svg>

现在这棵树如下:

defs

如果我们用一个 <defs> 元素包裹 #tree 组,这棵树就不会在画布上渲染。

<svg width="500.79px" height="200px" viewBox="0 0 500.79 200">
    <style type="text/css">
        #leaves{fill:#8CC63F;}
        #bark{fill:#A27729;}
    </style>
    <defs>
        <g id="tree">
            <!-- ... -->
        </g>
    </defs>
</svg>

现在这棵树就相当于一个模板。我们可以通过 <use> 元素来使用它,就像我们 use 其它元素一样。唯一的不同是 x y 属性现在是相对于用户坐标系统定位的,而不是相对于使用的元素。

例如,如果我们想要创建三个树的副本,然后在SVG画布上显示它们。假设在这种情况下,用户坐标系统匹配视窗的宽度和高度,初始位置也和SVG视窗的左上角重合,我们会得到如下的代码和结果:

<use xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#tree" x="50" y="100" />
<use xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#tree" x="200" y="100" />
<use xlink:href="http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html#tree" x="350" y="100" />

use

如上图所示,每棵树的定位都是相对于坐标系统的原点,在这里指的是SVG的左上角。所以每棵树的左上角都是定位在它自己在用户坐标系统中的位置( x , y ),独立于其它树以及 <defs> 中定义的树模板。

当你使用 <defs> 来重用元素,你可以给它应用不同的样式,给每棵树填充不同的颜色,只要这些样式没有在初始的树模板中定义。如果 <defs> 中的树已经使用了这些样式,这些样式同样没办法被新实例的样式覆盖。所以 <defs> 非常适合用于创建实例很少的模板,然后给副本应用其需要的样式。如果没有 <defs> ,只用 <use> 是不可能完成的。

<defs> 元素中的内容不是渲染树的一部分,就像 defs 是一个 g 元素,其 display 的值被设置为 none 。然而, defs 的子内容总是在源代码树中写出,然后被其它元素引用;因此, defs 元素或它的任何子内容的 display 属性的值都不能阻止这些元素被其它元素引用,即使设置为 none

使用 <symbol> 对元素进行分组

<symbol> 元素和 <g> 元素相似——它提供了一种对元素进行分组的方式。但是,它和分组元素有两个主要的不同:

  • <symbol> 元素不会被渲染。在这种方式中实际上它类似于 <defs> 元素。只有在 use 时才显示。
  • <symbol> 元素可以有自己的 viewBox preserveAspectRatio 属性。也就是它可以适应视窗,然后以你想要的任何方式渲染,而不是都按照默认的样式。

<symbol> 非常适用于定义可重复使用的元件(或符号)。它也可以作为 <use> 元素实例化的一个模板。而且有 viewBox preserveAspectRatio 属性,它可以在引用 <use> 元素定义的矩形视窗中自适应缩放。注意 symbol 元素每次被 use 元素实例化时都可以重新定义新的视窗。

此项功能是非常棒的,因为它允许你定义独立于它们渲染的视窗的元素,因此,确保你引用的 symbol 总是以某种方式显示在视窗中。

你需要先了解 viewBox 工作的方式,以及 preserveAspectratio 属性的值,这样才能最大地使用好这项功能。Chris Coyier写过一篇 文章 来解释为什么 <symbol> 元素是绘制图标的一个最好选择,以及如何使用。

我也将会写一篇介绍 viewport viewBox preserveAspectRatio 属性的文章,来解释这些属性的工作方式,以及如何在SVG中使用它们来控制和缩放图形。所以如果你有兴趣的话,敬请关注。

更新:文章已写好: 理解SVG坐标系统和转换(Part 1)——The viewport, viewBox , and preserveAspectRatio

w3cplus中文译文 ~

注意不能给给 symbol 元素应用 display 属性;因此,即使 display 属性的值设置为 none 之外的其它值, symbol 元素也不能直接渲染。但是即使 symbol 元素的 display 属性或任何它的父元素设置为 none symbol 元素也可以被引用。

总结

所有这些元素都是SVG中的容器结构元件,都有助于我们更容易地重用元素,也可保持代码的简洁性和可读性。这篇文章中我们提到的每个元素都有它自己的使用场景。现在你已经了解了每个元素的特性以及相互之间的区别,你可以自由选择使用哪个,根据需求决定。但是,不要忘了保持SVG的 可访问性

感谢您的阅读,希望这篇文章能帮到您!

本文根据 @SaraSoueidan 的《 STRUCTURING, GROUPING, AND REFERENCING IN SVG — THE <G> , <USE> , <DEFS> AND <SYMBOL> ELEMENTS 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: http://sarasoueidan.com/blog/structuring-grouping-referencing-in-svg/

彦子

在校学生,本科计算机专业。逗比一枚,热爱前端热爱生活,喜欢CSS喜欢JavaScript喜欢SVG,爱玩PS玩AI玩啊逗比的软件。努力向上,厚积薄发。

如需转载,烦请注明出处: http://www.w3cplus.com/svg/structuring-grouping-referencing-in-svg.html

作者:W3CPlus
原文地址:SVG中的结构化、分组和引用元素, 感谢原作者分享。

发表评论