【转】SVG之旅:SVG的图层和渲染顺序

本文转载自:https://www.w3cplus.com/svg/svg-layer.html

不管是在制图软件中还是Web页面的DOM元素,都有层的概念。在制图软件中,大家比较熟悉,能非常的清晰的看出图层的概念。而在Web页面中,特别是我们熟悉的HTML的DOM中,其实他也有层的概念。不同的是制图软件可以用鼠标拖动图层来改变层次,而DOM中需要依赖于CSS的z-index属性来控制他的层次关系。其实在SVG中,他也有层和渲染顺序的概念。今天我们就来看看SVG中的图层和渲染顺序相关的知识。

SVG的图层

首先我们来看SVG图层这个东东,相信只要使用过制图软件,比如Photoshop或者Sketch等,对于图层的认识并不会陌生。当然,你要是设计师出身的话,这方面的认识一定要远远高过工程师。前面也说过了,不管是在制作软件中,还是在Web页面中,都有图层的概念。比如我们经常接触的Web页面,能常常看到图片盖在图片上文字本盖在图片上等现象。事实上这些都是图层的应用,只是往往没有,只有。在制图软件中,为了方便理解由上到下的概念,在图层面板中的图层都是由上到下排列,上层会盖住下层。但在Web页面或者程序里,也因为逻辑规则,图层则是由下而上排列,下层会盖住上层(除非做了特殊处理)。

我们先来看看制图软件中的图层,比如Sketch的软件中:

SVG的图层

在制图软件中,控制图层比较方便,鼠标拖动就可以,比如下面的操作:

SVG的图层

通过Sketch可以很轻易的将刚才的绘制的图形转出.svg文件。操作步骤很简单,如下所示:

SVG的图层

这个时候,你可以通过文本编辑软件打开导出来的.svg文件。你可以看到SVG的代码(因为还没有正式学习怎么手动编写SVG代码),刚才导出的文件代码如下:

SVG的图层

有一大堆冗余的代码,现在先人肉来处理不需要的代码,后面的文章中将会介绍怎么通过工具来处理.svg中不需要的代码。处理完的代码如下:

<svg width="275px" height="275px" viewBox="0 0 275 275">
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <rect fill="#4A90E2" x="11" y="100" width="204" height="175"></rect>
        <polygon fill="#BD10E0" points="0 0 230.109375 35.8164062 89.5898438 221.050781"></polygon>
        <ellipse fill="#F02929" cx="179.5" cy="147.5" rx="95.5" ry="96.5"></ellipse>
    </g>
</svg>

如果你把这个.svg通过<img><object>元素引入,或者直接将代码内联到HTML文件中,你将看到的效果如下:

感觉有点偏主题了。回到正题中,如果你仔细看了代码之后,你会发现在制图软件中圆 -> 三角形 -> 矩形 。对应的图层顺序也是圆 -> 三角形 -> 矩形 ,但在代码中却不一样,反过来了矩形 -> 三角形 -> 圆。如图所示:

SVG的图层

了解了图层的规则后,我们看看SVG代码和Sketch里面的图层的对照。可以看出,SVG中的每一个元素都对应制图软件中的一个图层,比如<rect><polygon><ellipse>等元素。至于这些元素起什么作用,怎么用,暂且不在这篇文章中阐述。后面介绍SVG绘制图形一节中将会详细介绍这些绘图元素。

上面看一的是单个元素(单个图形)占一个独立的层。事实上,多个元素组成一个图形,那么这个情况又将会是什么样?同样先用制图软件来操作一下:

SVG的图层

制图软件中有两个层GroupGroup2,事实下每个图层中又有三个层:

SVG的图层

用同样的方法,将上面的图导出SVG文件,来看对应的SVG代码:

<svg width="364px" height="438px" viewBox="0 0 364 438">
    <g class="Group" transform="translate(23.000000, 0.000000)">
        <rect class="rect" fill="#4A90E2" x="11" y="100" width="204" height="175"></rect>
        <polygon class="triangle" fill="#BD10E0" points="0 0 230.109375 35.8164062 89.5898438 221.050781"></polygon>
        <ellipse class="circle" fill="#F02929" cx="245.5" cy="147.5" rx="95.5" ry="96.5"></ellipse>
    </g>
    <g class="Group-2" transform="translate(6.000000, 180.000000)" fill="#D0144B" stroke="#2BC14C" stroke-width="9">
        <polygon class="Star" points="105 146.25 43.2825485 176.379157 55.0695329 112.564578 5.13906579 67.370843 74.1412743 58.0604215 105 0 135.858726 58.0604215 204.860934 67.370843 154.930467 112.564578 166.717451 176.379157"></polygon>
        <polygon class="Star" points="237.580639 131.5 220.534867 139.843459 223.790319 122.171729 210 109.656541 229.057753 107.078271 237.580639 91 246.103525 107.078271 265.161278 109.656541 251.370958 122.171729 254.626411 139.843459"></polygon>
        <polygon class="Star" points="221.580639 210.5 204.534867 218.843459 207.790319 201.171729 194 188.656541 213.057753 186.078271 221.580639 170 230.103525 186.078271 249.161278 188.656541 235.370958 201.171729 238.626411 218.843459"></polygon>
        <polygon class="Star" points="126.580639 241.5 109.534867 249.843459 112.790319 232.171729 99 219.656541 118.057753 217.078271 126.580639 201 135.103525 217.078271 154.161278 219.656541 140.370958 232.171729 143.626411 249.843459"></polygon>
    </g>
</svg>

从代码中可以看出,如果一层里有多个元素时,在SVG中会用<g>元素来表示图形。看上去其实就是图层中嵌套了图层。

上面借助了Sketch制图软件让我们理解了SVG中的图层的概念。虽然里面的代码我们暂时还不明白其具体的意思,但这并不重要,因为后续的学习,我们就能清楚的知道他们具体的作用和怎么使用。

SVG渲染顺序

从上面的代码中可以看出,在文本编辑器里编写SVG代码就可以绘制出所需要的图形。那么SVG中绘制过程有自己的基本原则:

  • 解析顺序和绘制顺序一致,都要遵守XML中元素的位置排列。SVG中元素在XML中有固定的排列顺序,浏览器渲染时会遵守这个顺序,绘制时也同样会遵守这个顺序。也就是说先出现的元素会出现在绘制的底层,而后出现的元素会绘制在顶层,如果元素间的位置有重叠,则会现后绘制的元素会盖住先出现的元素
  • 子节点会继承父节点的一些属性(这个和CSS的属性有点类似),比如opacitytransform

整个SVG绘制处理过程可以用下图简单的来描述:

SVG的图层

不过在绘制SVG时,有一些细节需要注意:

  • 解析SVG文档时,忽略DTD验证:虽然是 DTD 是 XML 解析的标准验证方式,但是很多工具制作的 SVG,DTD 会缺失,所以解析时应该忽略 DTD 验证,不然会直接造成解析错误
  • 解析SVG文档时,一些元素的属性值可能有多种方式:多边形的点集,元素的 transform,都是一个数字集合,集合的分割方式可能是 空格,。 也可能是其他符号,所以在解析时需要兼容多种分割方式。颜色的表示,长度单位等,也可能会出现多种形式,如颜色有已知颜色和颜色值等形式,都需要做兼容
  • 元素的某些属性会继承父级元素transformopacity等属性,都需要考虑父级元素的继承关系。其中transform 会复杂一些,transform [3*2] 的 矩阵,会包括缩放、平移、旋转等信息,子元素的平移信息,需要和父级元素做缩放相乘后,再做平移
  • 元素属性的默认值:很多工具导出的 SVG,是会忽略一些属性的,而这些属性如果没有值,我们是没办法正确显示的。所以我们需要针对它们设置默认值。例如 fill 默认应该是 nonestroke 默认是 blackstroke-width 默认为1pxfill-rule 默认为 nonzero。这里重点说一下 fill-rule,它分为 evenodd 和 nonzero 两种方式
  • 解析顺序与渲染顺序,描边与填色的顺序:解析顺序和渲染顺序必须一致,并且和 XML 中的顺序一致,否则会出现错误的遮挡现象和绘制顺序倒转。描边和填色的顺序,基本原则是,单个元素的描边完成后,操作填色,然后再操作下一个元素。当然这里的填色可以灵活控制,比如保存所有填色,等所有描边完成后,一次性填色
  • 包含<image>标签的绘制:包含 <image> 标签的 SVG,处理起来会有些特殊的地方。这种 SVG 的存在,一般是设计师通过 Photoshop 编辑图片后,再导入 Sketch 中生成的 SVG。处理这种 SVG 的绘制时,基本思路是:解析 <image> 标签,当做 SVG 的底图,用一个透明遮罩挡住;然后解析后面的 <path> 标签,这是只需要解析 path 和 stroke,不需要 fill,用这里的 path 去涂抹底图,涂抹过的地方,透明遮罩失效,底图露出,就达到了涂抹出底图线条的目的

有一点需要特别提出来的是,在SVG中无法通过CSS的z-index来改变他们层级的。比如前面的示例:

<svg width="364px" height="338px" viewBox="0 0 364 338">
    <g class="group" style='z-index:1;'>
        <rect class="rect" fill="#4A90E2" x="11" y="100" width="204" height="175"></rect>
        <polygon class="triangle" fill="#BD10E0" points="0 0 230.109375 35.8164062 89.5898438 221.050781"></polygon>
        <ellipse class="circle" fill="#F02929" cx="245.5" cy="147.5" rx="95.5" ry="96.5"></ellipse>
    </g>
    <g class="group"  fill="#D0144B" stroke="#2BC14C" stroke-width="9" style='z-index:0;'>
        <polygon class="Star" points="105 146.25 43.2825485 176.379157 55.0695329 112.564578 5.13906579 67.370843 74.1412743 58.0604215 105 0 135.858726 58.0604215 204.860934 67.370843 154.930467 112.564578 166.717451 176.379157"></polygon>
        <polygon class="Star" points="237.580639 131.5 220.534867 139.843459 223.790319 122.171729 210 109.656541 229.057753 107.078271 237.580639 91 246.103525 107.078271 265.161278 109.656541 251.370958 122.171729 254.626411 139.843459"></polygon>
        <polygon class="Star" points="221.580639 210.5 204.534867 218.843459 207.790319 201.171729 194 188.656541 213.057753 186.078271 221.580639 170 230.103525 186.078271 249.161278 188.656541 235.370958 201.171729 238.626411 218.843459"></polygon>
        <polygon class="Star" points="126.580639 241.5 109.534867 249.843459 112.790319 232.171729 99 219.656541 118.057753 217.078271 126.580639 201 135.103525 217.078271 154.161278 219.656541 140.370958 232.171729 143.626411 249.843459"></polygon>
    </g>
</svg>

虽然在第一个group上设置了z-index:1,第二个group设置了z-index:0,但并没有如我们期望的一样,第一个group显示在第二个的上面,还是按照SVG的渲染顺序来渲染。

要解决这个问题,只能通过JavaScript来动态改变SVG的元素顺序。比如:

var svg = document.querySelector('svg');
var groups = document.querySelectorAll('.group')
for (var key in groups) {
    if (groups.hasOwnProperty(key)) {
        var element = groups[key];

        element.addEventListener('mouseenter', function (e) {
            svg.appendChild(e.target);
        }, false);
    }
}

最终效果如下:

注意:svg.appendChild(e.target); 对于已经存在的对象,则是移动了标签的位置。

总结

通过这一节的内容介绍,特别是借助于制图软件,让我们更好的理解了SVG的中图层中的概念,以及其渲染顺序。从而对SVG有了更深一层的了解。虽然这些都还只是SVG的基础,还不足以支承你做有意思的东东。但逐步进行下去,我们会有所收获的。

如果文章中有不对之处,还请路过大神拍正。如果你有更好的建议或经验,欢迎在下面的评论中与我们一起分享。如果你对这个系列的教程感兴趣的话,欢迎持续关注后续的更新。下一节,我们将学习SVG中的坐标系统。SVG的坐标系统相关的知识重要哟。

上一节下一节

如果觉得此文对你有帮助,赏杯咖啡,鼓励Ta创作出更多优秀文章!
打赏