Chapter 5. Document Structure
我们偶然提到过,SVG 允许您将文档的结构与其表示分离。在这一章中,我们将比较和对比这两者,更详细第讨论文档外观方面,然后展示一些 SVG 元素,这些元素您能够使文档结构更加清晰,更容易阅读,更容易维护。
Structure and Presentation
就像章节 1 提到的那样,在 Basic Shapes 中,XML的目的使提供一种方法来组织数据,并将这个结构从它的视觉呈现中分离出来。想一想那一章中画的猫。你识别出它是一只猫,是因为它的结构,几何形状的位置和大小构成了绘图。如果我们要进行结构性改变,例如缩短它的胡须,把鼻子变得更圆,让耳朵变得更长,耳朵的结尾处边等更圆滑。这个结构,因此,告诉你什么是图形(graphic).
这并不是说可视化样式的信息不重要;如果我们用紫色的粗线和灰色的内部来画这只猫,他就会被认出来是一只猫,但它的外观就不那么赏心悦目了。差异如图5-1所示。XML鼓励您分离结构(structure)和外观(presentation);不幸的是,许多关于XML的讨论都强调构(structure)而忽略了外观(presentation)。我们会纠正这个错误,通过详细介绍如何在 SVG 中指定外观(presentation)。
Using Styles with SVG
SVG 为了提供了四种方式指定一个图形各个方面的外观: 通过内联的样式(inline styles), 内部的样式表(internal stylesheets), 外部的样式表(external stylesheets), 和外观属性(presentation attributes)。让我们依次检查每一个。
Inline Styles
样例5-1 使用了内联样式(inline styles)。这正是我们目前使用外观信息的方式。我们设置 style 属性的值为一系列视觉属性,这些属性在附录B中的 Anatomy of a Style 中有详细描述。
Example5-1:Use of inline styles
<circle cx="20" cy="20" r="10" style="stroke: black; stroke-width: 1.5; fill: blue; fill-opacity: 0.6"/>
Internal Stylesheets
你不需要把您的 styles 放置到每个 SVG 元素当中;你可以创建一个内部的样式表(internal stylesheets)来收集公共使用的样式,可以应用一个特定元素出现的所有地方,
或者使用 named classes,将样式应用于特定的元素。样例5-2设置了一个内部样式表(internal stylesheets),该样式表将用淡蓝色的双虚线绘制圆,圆得内部使用淡黄色填充。
这个样式表写在 <defs>
元素中,我梦将在下一个章节中讨论。
这个例子接着绘制了一些圆。这些圆在图5-2的第二行,在内部样式表中具有覆盖规范的内联样式。
Example5-2. Use of internal stylesheet
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css"><![CDATA[
circle {
fill: #ffc;
stroke: blue;
stroke-width: 2;
stroke-dasharray: 5 3
}
]]></style>
</defs>
<circle cx="20" cy="20" r="10"/>
<circle cx="60" cy="20" r="15"/>
<circle cx="20" cy="60" r="10" style="fill: #cfc"/>
<circle cx="60" cy="60" r="15"
style="stroke-width: 1; stroke-dasharray: none;"/>
</svg>
External Stylesheets
如果您想将一组样式应用到多个 SVG 文档中,你可以拷贝这组内部样式(internal stylesheet),然后粘贴到每个要应用的文档中。这种方法,当然,如果您需要
对所有文档进行全局更改,那么对于大量文档来说,合适不切实际的。取而代之的,你应当取出 <style>
开始和结束标签之家的所有信息(不包括 <![CDTA[
和 ]]>
)。
然后保存到外部文件中,这个文件变成一个外部样式表(external stylesheet)。 样例5-3 显示了一个外部样式(external stylesheet), 保存在一个名为
ext_style.css 的文件中。这个样式使用了一系列的选中器(selectors), 包括 * ,它为所有没有其他样式的元素设置一个默认值,它与SVG一起,生成图5-3.
Example 5-3.External stylesheet
* { fill:none; stroke: black; } /* default for all elements */
rect { stroke-dasharray: 7 3; }
circle.yellow { fill: yellow; }
.thick { stroke-width: 5; }
.semiblue { fill:blue; fill-opacity: 0.5; }
样例5-4显示了一个完整的SVG文档(包括<?xml ...?>
,<?xml-stylesheet ...?>
和 <!DOCTYPE>
)和引用外部样式表的引用。
Example 5-4. SVG file that references an external stylesheet
<?xml version="1.0"?>
<?xml-stylesheet href="ext_style.css" type="text/css"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="200px" height="200px" viewBox="0 0 200 200">
<line x1="10" y1="10" x2="40" y2="10"/>
<rect x="10" y="20" width="40" height="30"/>
<circle class="yellow" cx="70" cy="20" r="10"/>
<polygon class="thick" points="60 50, 60 80, 90 80"/>
<polygon class="thick semiblue"
points="100 30, 150 30, 150 50, 130 50"/>
</svg>
注意:内联样式(Inline styles) 的渲染几乎总比内部样式表(internal stylesheet)或外部样式(external stylesheet)快;样式表(stylesheets)
和 classes 增加了渲染的时间,时间的长短取决于查询和解析的快慢。然而,样式表(stylesheets)更容易维护,更小的文件大小和缓存,使得文件加载速度更快。
Presentation Attributes
尽管绝大多数SVG文档将使用样式来表示信息,SVG 允许您已外观属性的形式指定这些信息。如果不用下面的表示方法:
<circle cx="10" cy="10" r="5" style="fill: red; stroke:black; stroke-width: 2;"/>
您可以将每个属性写成一个属性:
<circle cx="10" cy="10" r="5" fill="red" stroke="black" stroke-width="2"/>
如果你认为这是结构(structure)和外观(presentation)的混合,你是对的。外观属性确实会派上用场,但是,当您通过将XML数据源转换为SVG来创建SVG文档时。
你将会在 15 章中看到。在这些情况下,为每个外观属性创建一个独立的属性,会比创建一个独立的 style 属性内容更加容易。如果你展示的环境不支持样式表,
你可能也需要使用外观属性。
外观属性位于优先级列表的最底部。任何通过 内联样式(inline style),内部样式表(internal stylesheet),或外部样式表(external stylesheet)指定的样式都将覆盖
通过外观属性(presentation attribute)指定的样式,尽管外观属性(presentation attribute)覆盖继承的样式。在下面的SVG文档中,这个圆将会以红色填充,而不是绿色:
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css"><![CDATA[ circle { fill: red; } ]]></style>
</defs>
<circle cx="20" cy="20" r="15" fill="green"/>
</svg>
我们再一次强调,使用样式属性(style attributes) 或者 样式表(stylesheets) 应当一直是你的第一选择。样式表允许您将一系列复杂的填充和画笔特征,
应用到文档中所有拥有该效果的某些元素中,而不需要每个元素都有一份重复的信息。样式表的这种能力和灵活性,允许您已最少的工作量对多个文档的外观和感觉进行
重大调整。
Grouping and Referencing Objects
虽然我们可以将任何一幅画定义为一组无区别的形状(shapes)和线条(lines),但大多数非抽象艺术都是由一组形状(shapes)和线条(lines)组成的,
这些线条构成了可识别的命名对象。
The <g>
element
<g>
元素将它的所有子元素,集合为一个组,并且通常具有ID属性,来为这个组指定唯一的名称。每个组可能也由它自己的 <title>
和 <desc>
,
以便为基于文本的 XML应用程序提供表示,或者在不能展示的时候,提供展示。许多 SVG 渲染客户端,当你将鼠标悬停在该组内的任何图形上是,
将显示一个提示消息,内容为 <title>
元素的文本。屏幕前的读者将会看到 <title>
元素 和 <desc>
元素的内容。
除了概念上的清晰之外,这还来自于分组和记录对象的能力,<g>
元素还提供了符号上的便利。在开始的 <g>
标签中指定的任何样式都将应用于组中的
所有子元素。在样例5-5中,这样我们就不必在图5-4中所示的每个元素上复制 style="fill:none; stroke:black;"
。也可以将一个组嵌套在另一个组中,
尽管您在第6章之前不会看到任何示例。
元素类似于 Adobe Illustrator 等程序中的 Group Object 函数。它还提供了类似于此类程序中的层概念的功能;层也是关联对象的分组。
Example 5-5.Simple use of the g element
<svg width="240px" height="240px" viewBox="0 0 240 240" xmlns="http://www.w3.org/2000/svg">
<title>Grouped Drawing</title>
<desc>Stick-figure drawings of a house and people</desc>
<g id="house" style="fill: none; stroke: black;">
<desc>House with door</desc>
<rect x="6" y="50" width="60" height="60"/>
<polyline points="6 50, 36 9, 66 50"/>
<polyline points="36 110, 36 80, 50 80, 50 110"/>
</g>
<g id="man" style="fill: none; stroke: black;">
<desc>Male human</desc>
<circle cx="85" cy="56" r="10"/>
<line x1="85" y1="66" x2="85" y2="80"/>
<polyline points="76 104, 85 80, 94 104" />
<polyline points="76 70, 85 76, 94 70" />
</g>
<g id="woman" style="fill: none; stroke: black;">
<desc>Female human</desc>
<circle cx="110" cy="56" r="10"/>
<polyline points="110 66, 110 80, 100 90, 120 90, 110 80"/>
<line x1="104" y1="104" x2="108" y2="90"/>
<line x1="112" y1="90" x2="116" y2="104"/>
<polyline points="101 70, 110 76, 119 70" />
</g>
</svg>
The <use>
Element
复杂的图形通常包含重复的元素。例如,产品宣传册的每一页的左上角和右下角都可能有公司的 logo。如果你在一个图形设计程序中绘制这个宣传册,你只需要
绘制这个 logo 一次,将它的元素打包在一起,然后将它拷贝粘贴到其他要使用位置。 SVG 中的 <use>
元素为您提供了类似复制和粘贴功能的组(group),
您已经通过 <g>
元素或者任何单独的图形元素(例如你想要一个复杂的多边形形状,您只需要定义一次)。
一旦你定义了一组图形对象,你可以再一次展示它们,只需要通过 <use>
标签指定您想要重复利用的组,把它的 URI 设置到 xlink:href 属性中,
并指定 group 的(0,0)点应该移动到的位置的x坐标和y坐标。(我们将在第6章看到另一种实现这种效果的方法,在 The translate Transformation 章节。)
所以,为了创建另一个房子,并设置这些人物,如图5-5所示,你只需要把这些行放置在结束标签 </svg>
的前面:
<use xlink:href="#house" x="70" y="100"/>
<use xlink:href="#woman" x="-80" y="100"/>
<use xlink:href="#man" x="-30" y="100"/>
The <defs>
Element
你可能已经注意掉前面例子的一些缺陷:
- 决定将重用的男人和女生放置在哪里,数学上要求您知道原始的位置,并将其作为基础。而不是用一个简单的数字,比如零。
- 房子填充和画笔的颜色将会和原始图像建立联系,并不能被
<use>
所覆盖。这意味着你不能作一排五颜六色的房子。 - 这个文档绘制了三个组:女人,男人和房子。你不能“把它们存起来”,只画一组房子或一组人。
<defs>
元素(定义元素 definitions)解决了这些问题。通过将分组对象放在开始和结束的<defs>
标签之间。您指示 SVG 定义它们而不显示它们。
SVG 规范中,实际上,建议您将所有希望重用的对象放在 <defs>
元素之中,以便在流环境(streaming environment)中工作的 SVG 查看器可以更
有效地处理数据。在样例5-6 当中,房子,男人和女人都定义在左上角的原始坐标(0,0)处,并且房子没有设置任何颜色。因为这些组将放到<defs>
元素当中,
它们不会立马绘制到屏幕之上。而是作为一个模板(template)供将来使用。我们也创建了另外一个称为 couple 的房子,犯规来,使用了 man 和 woman 组。
(注意图5-6的底部的前半边不能使用 couple, 因为它用了不同的排列方式。)
<svg width="240px" height="240px" viewBox="0 0 240 240"
xmlns="http://www.w3.org/2000/svg">
<title>Grouped Drawing</title>
<desc>Stick-figure drawings of a house and people</desc>
<defs>
<g id="house" style="stroke: black;">
<desc>House with door</desc>
<rect x="0" y="41" width="60" height="60"/>
<polyline points="0 41, 30 0, 60 41"/>
<polyline points="30 101, 30 71, 44 71, 44 101"/>
</g>
<g id="man" style="fill: none; stroke: black;">
<desc>Male stick figure</desc>
<circle cx="10" cy="10" r="10"/>
<line x1="10" y1="20" x2="10" y2="44"/>
<polyline points="1 58, 10 44, 19 58"/>
<polyline points="1 24, 10 30, 19 24"/>
</g>
<g id="woman" style="fill: none; stroke: black;">
<desc>Female stick figure</desc>
<circle cx="10" cy="10" r="10"/>
<polyline points="10 20, 10 34, 0 44, 20 44, 10 34"/>
<line x1="4" y1="58" x2="8" y2="44"/>
<line x1="12" y1="44" x2="16" y2="58"/>
<polyline points="1 24, 10 30, 19 24" />
</g>
<g id="couple">
<desc>Male and female stick figures</desc>
<use xlink:href="#man" x="0" y="0"/>
<use xlink:href="#woman" x="25" y="0"/>
</g>
</defs>
<!-- make use of the defined groups -->
<use xlink:href="#house" x="0" y="0" style="fill: #cfc;"/>
<use xlink:href="#couple" x="70" y="40"/>
<use xlink:href="#house" x="120" y="0" style="fill: #99f;"/>
<use xlink:href="#couple" x="190" y="40"/>
<use xlink:href="#woman" x="0" y="145"/>
<use xlink:href="#man" x="25" y="145"/>
<use xlink:href="#house" x="65" y="105" style="fill: #c00;"/>
</svg>
<use>
元素不限于使用来自它所在文件的对象;xlink:href
属性可以指定任何有效的文件或URI。这使得在一个 SVG 文件中收集一组公共元素,并从其他文件中选择性地使用它们称为可能。例如,你可以创建一个名叫 identity.svg 的文件,它包了你结构中要使用的所有标识图形:
<g id="company_mascot">
<!-- drawing of company mascot -->
</g>
<g id="company_logo" style="stroke: none;">
<polygon points="0 20, 20 0, 40 20, 20 40" style="fill: #696;"/>
<rect x="7" y="7" width="26" height="26" style="fill: #c9c;"/>
</g>
<g id="partner_logo">
<!-- drawing of company partner's logo -->
</g>
然后通过下面的方式来引用它们:
<use xlink:href="identity.svg#company_logo" x="200" y="200"/>
警告:并不是所有的 SVG 查看器都支持外部引用,特别是网页浏览器,出于安全原因。一些浏览器(特别是 Internet Explorer)目前根本不支持外部文件引用。
其他的只允许<use>
元素来引用来自同一个域名或者同一台网络服务的文件,它被专门配置为允许跨源(cross-origin)使用。
The <symbol>
Element
<symbol>
元素提供了另外一种方法来分组元素。不像 <g>
元素, <symbol>
元素永远不会被显示,因此,您不必将它包含在 <defs>
规范中。
然而,这样做是习惯,因为 symbol 实际上是你为以后使用而定义的东西。symbol 还可以指定 viewBox 和 preserveAspectRatio 属性,通过向<use>
元素添加 width 和 height 属性,允许一个 symbol 是适应 viewport 的大小。样例5-7 显示对于一个简单 group(顶部的两个八边形)来说,忽略了
width 和 height 属性,但是在显示 symbol 时使用了 width 和 height。图5-7中右下边的八边形被切断了, 因为 preserveAspectRatio 被设置
成了 slice。 <rect>
元素用于显示每次使用的坐标。
Example 5-7. Symbols versus groups
<svg width="200px" height="200px" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<title>Symbols vs. groups</title>
<desc>Use</desc>
<defs>
<g id="octagon" style="stroke: black;">
<desc>Octagon as group</desc>
<polygon points="
36 25, 25 36, 11 36, 0 25,
0 11, 11 0, 25 0, 36 11"/>
</g>
<symbol id="sym-octagon" style="stroke: black;"
preserveAspectRatio="xMidYMid slice" viewBox="0 0 40 40">
<desc>Octagon as symbol</desc>
<polygon points="
36 25, 25 36, 11 36, 0 25,
0 11, 11 0, 25 0, 36 11"/>
</symbol>
</defs>
<g style="fill:none; stroke:gray">
<rect x="40" y="40" width="30" height="30"/>
<rect x="80" y="40" width="40" height="60"/>
<rect x="40" y="110" width="30" height="30"/>
<rect x="80" y="110" width="40" height="60"/>
</g>
<use xlink:href="#octagon" x="40" y="40" width="30" height="30"
style="fill: #c00;"/>
<use xlink:href="#octagon" x="80" y="40" width="40" height="60"
style="fill: #cc0;"/>
<use xlink:href="#sym-octagon" x="40" y="110" width="30" height="30"
style="fill: #cfc;"/>
<use xlink:href="#sym-octagon" x="80" y="110" width="40" height="60"
style="fill: #699;"/>
</svg>
The <image>
Element
<use>
让你唔够复用一个 SVG 文件中的一部分,<image>
元素包含了一个完整的 SVG 文件或者网格文件。如果你引入的是一个 SVG 文件,x, y, width 和 height 属性建立了引用文件绘制的视图窗口(viewport)。如果你包含的是一个网格文件,它将被缩放以使用属性值指定的矩形区域。 SVG 规范要求查看器同时支持JPEG和PNG两种网格类型的文件;查看器可能支持其他文件。例如,大部分浏览器将迟迟GIF。样例5-8展示了如何使用SVG包含JPEG图像。结果如图5-8所示。
Example 5-8. Use of the image element
<svg width="310px" height="310px" viewBox="0 0 310 310"
xmlns="http://www.w3.org/2000/svg">
<ellipse cx="154" cy="154" rx="150" ry="120" style="fill: #999999;"/> <!-- 1 -->
<ellipse cx="152" cy="152" rx="150" ry="120" style="fill: #cceeff;"/> <!-- 2 -->
<image xlink:href="kwanghwamun.jpg"
x="72" y="92"
width="160" height="120"/> <!-- 3,4,5 -->
</svg>
- 注释1: 创建一个灰色的椭圆以模拟一个阴影。
- 注释2: 创建主蓝色椭圆,因为它出现在灰色椭圆之后,所以它显示在灰色椭圆上面。
- 注释3: 通过
xlink:href
指定要引入文件的URI。 - 注释4: 通过 x 和 y 指定图像的左上角。
- 注释5: 通过 width 和 height 指定图片需要进行缩放的比例。
<image>
元素可以有一个 preserveAspectRatio 属性,来指示当图像文件的尺寸与元素的 width 和 height 不匹配时应做什么。默认的值是:xMidYMid meet,将缩放图像,以适应并居中于你指定的矩形区域(参考 Preserving Aspect Ratio 章节)。如果你包含的是一个 SVG 文件,您可以在 preserveAspectRatio 值的开头添加关键字 defer(比如 defer xMidYMin meet);如果被包含的图像有 preserveAspectRatio 属性,就使用这个属性。
- [7] In Chapter 11, we’ll see another way to create a drop shadow in Creating a Drop Shadow.