前言
上一篇文章中介绍了 path 的用法以及常见的 path 动画的简单介绍。这一篇中,我们将介绍 SVG 中的容器元素及一些特别的容器特性,以此可以实现:组合复用、图形标记、元素纹理、元素遮罩、穿孔效果、元素填充动画,下面将通过示例代码和说明一一介绍。
往期文章
容器元素
日常开发中可能用到的容器元素如下:
- <a>
- <defs>
- <g>
- <symbol>
- <marker>
- <mask>
- <pattern>
a 超链接
svg 版本超链接,用法和 Html 的 a 标签基本上一样。大部分浏览器会自动区分 SVG 内的 a 标签
外链网页和本页锚点跳转
例1:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<!-- 跳转网页 -->
<a class="border" href="https://developer.mozilla.org/en-US/docs/SVG" target="_blank">
<rect x="10" y="10" width="120" height="80" fill="green" />
</a>
<!-- 同 a 标签一样可以设置锚点进行本页跳转 -->
<a class="border" xlink:href="#test">
<text x="20" y="140"
font-family="Verdana"
font-size="32">
点我跳转
</text>
</a>
</svg>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<div id="test">你好啊</div>
<br><br><br><br><br><br><br><br><br><br>
图形元素的组合复用
一个火柴人的图形,是由头(circle)、身体(line)、双手(line)和双脚(line)共同组成的。那么如果我们需要画多个火柴人,为了避免重复画相同的部件,可以使用 g 或者 symbol 标签将这些部件当作一个整体去重复使用。
g 的使用
g 的基本使用及属性叠加后的优先级
例2:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<!-- 在 g 元素上设置的部分属性可以被 g 元素内的子元素继承 -->
<g id="test1" fill="green" stroke="orange">
<circle cx="0" cy="0" r="10" fill="transparent"/>
<!-- 身体 -->
<path d="M 0 10 l 0 30"></path>、
<!-- 双手 -->
<path d="M 0 15 l -10 20"></path>
<path d="M 0 15 l 10 20"></path>
<!-- 双脚 -->
<path d="M 0 40 l -10 20"></path>
<path d="M 0 40 l 10 20"></path>
</g>
<g id="test2" fill="transparent">
<circle cx="0" cy="0" r="10" />
<path d="M 0 10 l 0 30"></path>、
<path d="M 0 15 l -10 20"></path>
<path d="M 0 15 l 10 20"></path>
<path d="M 0 40 l -10 20"></path>
<path d="M 0 40 l 10 20"></path>
</g>
<!-- svg 的属性也有自己的作用域,会优先使用离自己最近的属性值 -->
<!-- use1 圆的填充使用的是 circle 的 fill,整体线的颜色使用的是所在 g 的 stroke -->
<use id="use1" x="20" y="120" fill="red" stroke="blue" xlink:href="#test1"></use>
<!-- use2 圆的填充使用的是 g 的 fill,整体线的颜色使用的是所在 use 的 stroke -->
<use id="use2" x="120" y="120" fill="red" stroke="blue" xlink:href="#test2"></use>
</svg>
通过这里例子我们可以看以下特点
- 被
g元素包裹的图形元素会直接显示 - 被
g元素包裹的图形可以被当作一个整体被use去使用 g和use都可以设定一些图形的属性,属性将会继承给被包裹的元素use在复用的图形的时候必须要给定一个节点- 属性也有自己的作用域,在多个属性叠加的情况下,会优先选择离自己近的
这里需要注意的是 SVG 的显示属性是可以在 css 中使用的。那么如果存在 class 设置相同属性的情况下,结果会是如何呢?
class 与设定在 svg 属性的优先级
例3:
下面列出了可能出现的几种情况及结果
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<!-- 同一个元素中在属性中设置fill的同时也在 class 中设置 fill。同元素的 class 的优先级高 -->
<rect x="0" y="0" width="30" height="30" fill="green" class="fill-red"/>
<!-- 子元素继承父级属性中定义的 fill 的同时在子元素 class 中设置 fill。子元素 class 的优先级高 -->
<g fill="green">
<rect x="0" y="40" width="30" height="30" class="fill-red"/>
</g>
<!-- 子元素继承父级 class 中定义的 fill 的同时在子元素属性中设置 fill。子元素属性的优先级高 -->
<g class="fill-red">
<rect x="0" y="80" width="30" height="30" fill="green"/>
</g>
<!-- 子元素继承的是父元素 class 定义的属性 -->
<g fill="green" class="fill-red">
<rect x="0" y="120" width="30" height="30" />
</g>
</svg>
一句话概括就是同一个元素优先选用 class 内定义的属性,没有则用自身上定义的属性。如不自身没有则从父元素继承,父元素也遵从 class 属性优先的原则。
symbol 的使用
symbol 标签同 g 可以组合复用元素,不同于g的地方是被symbol包裹的元素不会显示,且只会展示可视区范围内的图像
将例子 1 中的g换成symbol
例4:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<symbol id="test1" fill="green" stroke="orange" viewBox="0 0 200 200">
<circle cx="0" cy="0" r="10" fill="transparent"/>
<!-- 身体 -->
<path d="M 0 10 l 0 30"></path>、
<!-- 双手 -->
<path d="M 0 15 l -10 20"></path>
<path d="M 0 15 l 10 20"></path>
<!-- 双脚 -->
<path d="M 0 40 l -10 20"></path>
<path d="M 0 40 l 10 20"></path>
</symbol>
<use id="use1" x="20" y="120" fill="red" stroke="blue" xlink:href="#test1"></use>
</svg>
谷歌、edge、火狐都是这样,只展示 SVG 元素 viewBox 可视区的部分
defs 定义引用
SVG 允许我们定义以后需要重复使用的图形元素。 建议把所有需要再次使用的引用元素定义在
defs元素里面。这样做可以增加SVG内容的易读性和可访问性。 在defs元素中定义的图形元素不会直接呈现。 你可以在你的视口的任意地方利用<use>元素呈现这些元素。
defs 最大的作用就是为了声明变量和方便复用。defs 内包裹的元素没有顺序和数量的限制
defs 可包裹的元素有:动画元素、描述性元素、形状元素、结构化元素、渐变元素
defs 声明之后 id 索引全局可用,且同名的 id 不覆盖(id 是唯一的,会优先用前者)。和普通标签 id 一致。
defs内定义的元素不会被显示
例5:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<g id="test1" fill="green" stroke="orange" viewBox="0 0 200 200">
<circle cx="0" cy="0" r="10" fill="transparent"/>
<!-- 身体 -->
<path d="M 0 10 l 0 30"></path>、
<!-- 双手 -->
<path d="M 0 15 l -10 20"></path>
<path d="M 0 15 l 10 20"></path>
<!-- 双脚 -->
<path d="M 0 40 l -10 20"></path>
<path d="M 0 40 l 10 20"></path>
</g>
</defs>
<use id="use1" x="20" y="120" fill="red" stroke="blue" xlink:href="#test1"></use>
</svg>
marker 路径的结束位的标记图形
marker元素定义了在特定的<path>元素、<line>元素、<polyline>元素或者<polygon>元素上绘制的箭头或者多边标记图形。
专用属性:
- refX :x 方向的偏移
- refY :y 方向的偏移
- markerWidth :marker 容器的宽,可以控制marker大小
- markerHeight :marker 容器的高,可以控制marker大小
- orient :marker 容器的旋转角度
marker 容器可以定义 viewBox 属性。和 SVG 元素一样会生成一个独立的坐标系。markerWidth、markerHeight 设置容器的宽和高。和SVG的 width 和 height 一样。只不过 markerWidth、markerHeight 没有具体单位,具体的长宽和所在SVG坐标系的单位长度有关,经典套娃。
这里需要注意的是marker坐标系的初始方向和所在线的衍生方向有关。marker坐标系的 x 轴方向,同线的衍生方向(曲线的话是切线方向)。y 轴以 x 轴逆时针旋转 90°。
具体可以见下图
简单使用marker
例6:
可以分别在线的开始、连接处、结尾三个地方应用marker
stroke-width 会增加 marker 的大小, marker 也在 stroke 的规则范围内
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
markerWidth="4" markerHeight="4" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="orange"/>
</marker>
<marker id="rect" viewBox="0 0 10 10" refX="1" refY="5"
markerWidth="4" markerHeight="4" orient="auto">
<rect x="0" y="0" width="10" height="10" fill="blue"></rect>
</marker>
<marker id="circle" viewBox="0 0 10 10" refX="1" refY="5"
markerWidth="4" markerHeight="4" orient="auto">
<circle cx="5" cy="5" r="5" fill="red"></circle>
</marker>
</defs>
<!-- 结尾处应用 marker -->
<line x1="10" y1="20" x2="150" y2="20" stroke="orange" marker-end="url(#Triangle)"/>
<!-- 开始处应用 marker, stroke-width 会增加 marker 的大小 -->
<line x1="10" y1="40" x2="150" y2="40" stroke="red" stroke-width="3" marker-start="url(#circle)"/>
<!-- 连接处应用 marker -->
<polyline fill="none" stroke="blue" points="10,60 150,60 150,100 180,100" marker-mid="url(#rect)"/>
<!-- 三处使用不同的 marker -->
<polyline fill="none" transform="translate(0,60)" stroke="blue" points="10,60 150,60 150,100 180,100" marker-start="url(#circle)" marker-mid="url(#rect)" marker-end="url(#Triangle)" />
</svg>
pattern 纹理
在 pattern 内容绘制的内容可用作为 fill 或 stroke 的纹理进行填充。之所以叫它纹理,是因为填充的部分会被平铺直至占满整个内容。
属性:
- x:x 坐标
- y:y 坐标
- width:pattern 容器的宽
- height:pattern 容器的高
- patternUnits:pattern 容器使用的的坐标系
- patternContentUnits:pattern 容器绘制内容的的坐标系
- patternTransform:对 pattern 进行变形,用法同 transform
- xlink:href:引用外部的纹理
- preserveAspectRatio:显示模式,可以设置是否对显示区进行缩放或裁剪
简单的使用 pattern。 fill 与 stroke 的对比
例7:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<pattern id="Pattern0" x="0" y="0" width="1" height="1">
<rect x="0" y="0" width="80" height="80" fill="skyblue" stroke="red"/>
<circle cx="40" cy="40" r="30" fill="green" stroke="yellow">
</pattern>
</defs>
<!-- 矩形 fill 填充纹理 -->
<rect fill="url(#Pattern0)" x="0" y="0" width="100" height="100" stroke="red"/>
<!-- 圆 fill 填充纹理 -->
<circle cx="100" cy="140" r="50" fill="url(#Pattern0)" stroke="red"/>
<!-- 非闭合的图形的 stroke 是无法使用 pattern 的-->
<line x1="80" y1="40" x2="180" y2="40" stroke="url(#Pattern0)" stroke-width="20"></line>
</svg>
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<!-- 矩形 stroke 填充纹理 -->
<rect fill="red" x="60" y="25" width="80" height="80" stroke="url(#Pattern0)" stroke-width="40"/>
<!-- 闭合的 path 填充纹理 -->
<path d="M 10 160 l 100 0 l 0 40 Z" stroke="url(#Pattern0)" stroke-width="40"></path>>
</svg>
我们可以看到在 1 图中的并没有出现平铺的效果,原因是因为 pattern 容器内绘制的图形并没有占满整个pattern 容器,而平铺是根据 pattern 容器的内容进行平铺的。
在例子中,pattern 容器的大小等于被应用的图形大小(矩形和圆),所以 pattern 容器内没用绘制图形的部分就出现了留白的情况
pattern 容器的坐标系
在上面的例子中,看起来这个纹理杂乱无章,其实它还是有着一个自己的规则。下面将详细说明,pattern 容器的区域和绘制内容的关系。
在 pattern 容器有着一个自己的坐标系,坐标系的规则同 SVG。可以通过 patternUnits 和 patternContentUnits 两个属性来控制
patternUnits
patternUnits 用来设置pattern容器适用于哪个坐标系
可以用值:
- userSpaceOnUse: 使用该纹理被应用的图形所在的坐标系
- objectBoundingBox(默认): 使用一个独立的坐标系统。以该纹理被应用的图形大小作为参照来建立坐标系。可视区的范围以百分比的形式划定,在这个坐标系下通常用 0-1 来表示大小。相当于设置了一个 viewBox="0 0 1 1"。
patternContentUnits
patternContentUnits 用来设置pattern容器内定义的图形元素适用于哪个坐标系
可以用值:
- userSpaceOnUse(默认): 使用该纹理被应用的图形所在的坐标系
- objectBoundingBox: 使用一个独立的坐标系统。以该纹理被应用的图形大小作为参照来建立坐标系。可视区的范围以百分比的形式划定,在这个坐标系下通常用 0-1 来表示大小。相当于设置了一个 viewBox="0 0 1 1"。
通过下面的例子会更好的理解这两个属性
两者同用 objectBoundingBox 实现自适应大小的平铺
例8:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<!-- 随着容器大小改变纹理大小-->
<!-- 这里的容器大小为纹理被应用图形的 0.5 倍(n * 0.5)-->
<pattern id="Pattern5" x="0" y="0" width=".5" height=".5" patternContentUnits="objectBoundingBox">
<!-- 同理矩形的宽:n * 0.5 高:n * 0.5 stroke-width:n * 0.02-->
<rect x="0" y="0" width="0.5" height="0.5" fill="skyblue" stroke="red" stroke-width="0.01"/>
<!-- 圆cx: n * 0.25 cy: n * 0.25 r: n * 0.2 stroke-width:n * 0.01-->
<circle cx="0.25" cy="0.25" r=".2" fill="green" stroke="yellow" stroke-width="0.01">
</pattern>
</defs>
<!-- 宽高200的矩形,pattern容器宽高参照值为 100 -->
<rect fill="url(#Pattern5)" stroke="black" x="0" y="0" width="200" height="200"/>
</svg>
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<!-- 宽高100的矩形,pattern容器宽高参照值为 50 -->
<rect fill="url(#Pattern5)" stroke="black" x="0" y="0" width="100" height="100"/>
</svg>
mask 蒙版
在SVG中,你可以指一个透明的遮罩层和当前对象合成,形成背景。透明遮罩层可以是任何其他图形对象或者<g>元素。mask元素用于定义这样的遮罩元素。属性mask用来引用一个遮罩元素。
专有属性:
- x :x 坐标
- y :y 坐标
- width mask 容器的宽,默认值为 120%
- height :mask 容器的高,默认值为 120%
- maskUnits :mask 容器使用的坐标系(同 pattern)
- maskContentUnits: mask 内容区域使用的坐标系(同 pattern)
mask 内图形的颜色不会改变作用元素的颜色。mask 内的颜色表现为越接近 #FFFFFF 越透明(合成后作用元素的颜色越明显),反之越接近 #000000 越不透明(合成后作用元素的颜色越淡越不明显)。通过这个特性可以实现穿孔的效果。
mask 的颜色的影响
例9:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<mask id="Mask1">
<!-- mask 内图形的颜色不会改变作用元素的颜色,只影响遮罩的透度,越接近白色越透 -->
<rect x="0" y="0" width="200" height="200" fill="#FF0000" />
<circle cx="100" cy="100" r="50" fill="#FFFFFF" />
</mask>
</defs>
<rect x="0" y="0" width="200" height="200" fill="green" mask="url(#Mask1)"/>
</svg>
通过 mask 实现穿孔
例10:
在红色的矩形中心打一个圆形,通过这个这个圆形可以看到后面的蓝色矩形的部分
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<mask id="Mask3">
<!-- 中心留空 -->
<rect width="100%" height="100%" fill="#FFFFFF"/>
<circle cx="100" cy="100" r="50" fill="#000000" >
</mask>
</defs>
<!-- 底层的蓝色矩形 -->
<rect x="0" y="0" width="200" height="200" fill="blue" />
<!-- 中心留空的红色矩形 -->
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#Mask3)"/>
</svg>
通过 mask 实现填充动画
例11:
给定一个固定位置的 mask,通过改变作用元素与 mask 的位置关系实现动态填充效果
这里的动画借助了anime动画库来快速实现
爱心的svg
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641870835466" id="like" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2888" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M667.786667 117.333333C832.864 117.333333 938.666667 249.706667 938.666667 427.861333c0 138.250667-125.098667 290.506667-371.573334 461.589334a96.768 96.768 0 0 1-110.186666 0C210.432 718.368 85.333333 566.112 85.333333 427.861333 85.333333 249.706667 191.136 117.333333 356.213333 117.333333c59.616 0 100.053333 20.832 155.786667 68.096C567.744 138.176 608.170667 117.333333 667.786667 117.333333z" p-id="2889"></path></svg>
实现代码如下:
<svg class="border" width="200" height="200" viewBox="0 0 200 200">
<defs>
<mask id="Mask5">
<use href="../../../static/like.svg#like" fill="#FFFFFF" ></use>
</mask>
</defs>
<rect id="rect" x="0" y="180" width="200" height="200" fill="#E91E63" mask="url(#Mask5)" />
<use href="../../../static/like.svg#like" fill="transparent" stroke="#E91E63" stroke-width="20"></use>
</svg>
<div class="input">
<div>输入 0 - 100</div>
<input type="text" onchange="setLikeValue(event)">
</div>
function setLikeValue(e){
const value = e.target.value;
const reg = /^[0-9]+$/;
if(!reg.test(value) || +value > 100) return
// 初始 y - 高度所占高
anime({
targets: '#rect',
y:180 - (+value/100) * 160
});
}
效果如下:
写在最后
这次几次的更新有点慢,超出的我的预期,原计划2月份应该能全部更新完的,结果回头看之前记的笔记内容发现了不少问题,重新修改案例整理文案花了不少时间。
这个月肯定来不及了,这个系列我争取早点更新完,正好也复习回顾一下,如果说文章里面有说的不对的地方也欢迎来指正,我会及时修改。
参考资料