《深入理解SVG》读书笔记

突然在电子书架上发现了《深入理解SVG》这一本书(已经忘记是什么时候买的了。细想一下,虽然目前在项目中会接触到SVG,但貌似并没有系统学习过相关的知识,决定阅读一下,并补充相关的知识点和笔记。

<!--more-->

1. 基础

SVG全称可缩放矢量图形Scalable Vector Graphics,SVG)。

但SVG并不仅仅是图片。其他图片格式是告诉计算机在屏幕上哪一点应该绘制什么颜色,而 SVG 是通过代码来绘制图形的,它是一个结构化的文档格式,包含了图形的几何结构、样式信息等。

SVG 相对于图像,就好比 HTML相对于文本。

一个简单的SVG文档包含在 <svg> 元素中,可以指定宽度和高度,如下所示:

<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
  <!-- SVG 内容将在这里 -->
</svg>

SVG 使用的坐标系统或者说网格系统,跟canvas差不多,以左上角为原点,向右x轴为正,向下y轴为正。

<svg width="200" height="200" viewBox="0 0 100 100">
  <rect width="100" height="100" fill="red" />
</svg>

画布大小为200x200,但viewBox指定了只展示0 0 100 100这个区域的内容,这部分内容会放在整个画布上,所以看起来像是图形放大了2倍一样。

viewBox 允许你创建可缩放的图形,而不受固定宽度和高度的限制,这也是SVG一个非常重要的特性。

1.1. 形状

SVG 支持多种基本形状,包括矩形、圆形、椭圆、线段和多边形。例如,下面的代码将创建一个红色矩形:

<rect width="100" height="50" fill="red" />

使用 <circle> 元素可以创建圆形,使用 <ellipse> 元素可以创建椭圆。例如:

<circle cx="50" cy="50" r="30" fill="green" />
<ellipse cx="120" cy="70" rx="40" ry="25" fill="orange" />

使用 <line> 元素创建线段

<line x1="10" y1="10" x2="90" y2="90" stroke="black" />

使用 <polygon> 元素创建多边形

<polygon points="50 5, 90 90, 10 90" fill="purple" />

使用 <text> 元素可以在SVG中添加文本。例如:

<text x="20" y="40" font-family="Arial" font-size="20" fill="brown">Hello SVG!</text>

使用 <path> 元素可以创建复杂的形状。路径数据由一系列命令和参数组成,例如:

<path d="M10 10 L50 10 L30 50 Z" fill="blue" />

这个例子描述了一个由三个线段组成的三角形,简单来说,path的语法是一串指令 + 参数列表合并的内容

指令1 参数1_1 [...参数1_N] 指令2 参数2_1 [...参数2_N]

使用path,能够以一定的精确度,定制你能想象的任何形状。

常见的指令有M(移动)、L(直线)、H(水平线)、V(竖直线)、Z(闭合)、C(三次贝塞尔曲线)、A(弧线)等,更多的path语法,可以参考path - MDN

1.2. SVG渲染模型

渲染模型指的是计算机解析 SVG 标记和样式来生成图画的过程。SVG 的渲染模型被称为画家模型。就像在墙上涂层绘制,后绘制的元素会覆盖前面的元素。

  • fill属性用于设置图形的填充,默认是黑色,取值为颜色值或者渲染服务的引用
  • fill-rule用来指定内部有洞的 <path> 元素以及路径、多边形和纵横交错的折线应该如何填充
  • stroke属性用于设置图形的描边,意思是指沿着它的边画一条线,默认没有描边。取值为颜色值或者渲染服务的引用。
  • 描边和填充会按照绘制顺序层叠显示,stroke默认在fill之上。SVG 2 中的paint-order属性可以调整顺序。
  • opacity应用在整个元素上,是整体透明度。fill-opacity和stroke-opacity只影响填充和描边的透明度。
  • shape-rendering、text-rendering等属性提示浏览器使用何种优化方法绘制图形,比如速度优先还是质量优先。
  • z-index属性可以调整SVG元素的渲染顺序,不支持的浏览器只能通过改变DOM顺序来调整。

2. 颜色

这本书用了大量的篇幅来介绍svg中相关的颜色。

2.1. 颜色表示法

在物理学中,颜色是表示波的频率或者光的能级的一个属性,光谱是连续的,颜色之间并没有明确的界限。我们眼睛看到的颜色是三种色素不同值的混合。

几乎每一种使用颜色来传递信息的设备,都利用了颜色混合。

  • 打印机使用减色混合,会使用颜料反射到你眼睛中的颜色中消除它吸收的那部分光的频率
  • 屏幕使用加色模型,多种颜色组合来增加进入你眼中的光的总量

大多数现代电脑可以支持每种颜色 256 个级别(0~255),超过 1600 万种组合。这是在 Web 中使用颜色编码的基础

可以使用下面的方式来表示颜色

  • 颜色关键字,在早期的 HTML 和 CSS 版本中引入的简单颜色关键词集,如redgold等语义化的颜色名词

  • 函数表示法,格式为 rgb(red,green,blue)

  • 十六进制表示法,格式为#RRGGBB#RGB

  • HSL表示法,色相 - 饱和度 - 亮度(hue-saturation-lightness)模型把颜色描述为“纯”色和黑色、白色或灰色的混合方式。

CSS 以及大部分 SVG 默认使用的都是 sRGB(标准 RGB)模型。SVG 规范定义了 color-interpolation 属性,它允许使用者切换到更简单的数学线性混合颜色模式(linearRGB 模式)

2.2. 透明度

SVG 使用三个不同的属性来控制基础形状和文本的不透明度:opacity、 fill-opacity 以及 stroke-opacity。

Web 中的不透明度通常使用一个介于 0.0(不可见)和 1.0(纯色,不透明)之间的小数来表示

opacity 属性应用在设置它的元素上,它的值在确定两个重合形状或同一形状填充和描边的重叠部分中每一点的最终颜色之后添加。

而设置 stroke-opacityfill-opacity,再使用 rgbahsla 颜色函数时,透明效果在每个形状绘制时只作用在有颜色的部分。

<svg width="300" height="300" viewBox="0 0 300 300">
    <rect x="10" y="10" width="100" height="100" fill="red" fill-opacity="0.5" stroke-width="10" stroke-opacity="0.5"
      stroke="purple" />
    <rect x="130" y="10" width="100" height="100" fill="red" stroke-width="10" stroke="purple" opacity="0.5" />
  </svg>

可以看到这两种设置的区别

3. 渲染服务

3.1. 概念

fillstroke 的值比单一颜色更加复杂时,SVG提供了一种叫做渲染服务(Paint Servers)的概念来描述图形是如何被渲染的。

渲染服务是SVG中用于填充和描边的图形内容,可以是渐变、图案等,可以简单理解为SVG中可以复用的渲染内容(一个颜色、一段渐变、一个图案甚至是文本或者其他的SVG文件)。

通过defs标签定义渲染服务,一个defs标签中可以包含一个或多个可以重复使用的元素,每个元素使用唯一的id进行命名。

比如下面定义了两个不同的渐变色

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <!-- 定义多个渐变 -->
  <defs>
    <!-- 第一个渐变 -->
    <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
      <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
    </linearGradient>

    <!-- 第二个渐变 -->
    <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:rgb(0,255,0);stop-opacity:1" />
      <stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
    </linearGradient>
  </defs>
    <!--后续可以使用...-->
</svg>

渲染服务本身不直接绘制内容,需要被图形元素引用才会起作用。定义好渲染服务之后,在fillstroke属性中使用url()函数引用渲染服务的id,格式为url(#xxid)

  <!-- 使用第一个渐变填充矩形 -->
  <rect x="10" y="10" width="90" height="90" fill="url(#gradient1)" />

  <!-- 使用第二个渐变填充另一个矩形 -->
  <rect x="110" y="10" width="90" height="90" fill="url(#gradient2)" />

为了避免兼容问题,url第二个参数可以指定一个备用纯色,当服务引用失败时会使用该备用色。

可以用单色渐变的方式定义命名颜色,这种颜色值可以被多个图形引用,便于维护。

<linearGradient id="AcmeRed">
    <stop stop-color="#FF4022" />
</linearGradient>

建议所有的渲染服务都在定义之后才引用,避免某些浏览器出现不支持的情况。

3.2. 渐变

渐变是从一种颜色或不透明状态平滑过渡到另一种颜色或不透明状态。SVG 目前支持两种类型的渐变:线性渐变(直线方向)和径向渐变(环形)。

在渲染服务这里已经展示了渐变的基础语法:通过linearGradientstop来创建线性渐变(径向渐变的标签为radialGradient,其他语法与线性渐变比较相似

<linearGradient id="red-blue">
    <stop stop-color="red" offset="0"/>
    <stop stop-color="lightSkyBlue" offset="1"/>
 </linearGradient>

stop标签有一些比较重要的属性

  • stop-color:颜色值
  • stop-opacity:颜色的不透明度
  • offset:颜色结点的位置,值在0-1范围内,超出范围的地方会用纯色填充(默认值,后面会介绍使用repeat

通过渐变矢量可以指定线性渐变的方向,注意下面的x1y1x2y2这些属性

<linearGradient id="blue-green" x1="0" y1="0" x2="100%" y2="100%" >
    <stop stop-color="blue" offset="0"/>
    <stop stop-color="darkSeaGreen" offset="1"/>
</linearGradient>

此外还可以通过渐变标签的gradientTransform属性来控制线性渐变的方向,看名字跟transform有点像。

变换一个渐变是什么意思呢?它的意思是渲染服务创建的墙纸在裁剪为要填充的形状之前进行了变换。比如一个水平方向的渐变,通过变换旋转90度,就可以变成一个数值方向的渐变。

相比之下,如果直接变换一个使用渐变填充的形状时,形状和渐变都会被变换。

下面是书中举的一个例子,比较有代表性

  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="500"
    viewBox="0,0 400,500">
    <linearGradient id="stripe" x2="0" y2="100%">
      <stop offset="0" stop-color="yellow" />
      <stop offset="0.1" stop-color="gold" />
      <stop offset="0.5" stop-color="tomato" />
      <stop offset="0.5" stop-color="blueViolet" />
      <stop offset="0.9" stop-color="indigo" />
      <stop offset="1" stop-color="midnightblue" />
    </linearGradient>
    <linearGradient id="stripe-transformed" xlink:href="#stripe" gradientTransform="skewY(25)" />

    <g fill="url(#stripe)">
      <rect height="190" width="190" x="5" y="5" />
      <rect height="190" width="190" x="5" y="5" transform="translate(200,0) skewY(25)" />
    </g>
    <g transform="translate(0,200)" fill="url(#stripe-transformed)">
      <rect height="190" width="190" x="5" y="5" />
      <rect height="190" width="190" x="5" y="5" transform="translate(200,0) skewY(25)" />
    </g>
  </svg>

超出渐变矢量终点部分的渐变外观可以通过 <linearGradient> 元素上 spreadMethod 属性的值来设置。它控制着渐变如何无限扩展,该属性默认值pad,可以设置为

  • repeat,渐变结点会从始至终以相同的顺序重复,如果起点和终点颜色不同,则中间看起来会不连贯
  • reflect(每个重复周期内,都会反转结点的顺序,保证起点和终点之间有平滑过渡)

3.3. 图案

在SVG中,<pattern> 元素用于定义一个可重复的图案,该图案可以在SVG图形中的其他元素中使用。

pattern 就像是橡皮图章,你可以在画布上面一次一次地盖章,就会出现重复的图案,也可以类比成瓷砖上的花纹。

  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="4in" height="3in">
    <pattern id="stripes" x="5%" width="10%" height="10%" stroke-width="5">
      <line x1="3" x2="3" y2="100%" stroke="maroon" />
      <line x1="9" x2="9" y2="100%" stroke="gold" />
      <line x1="15" x2="15" y2="100%" stroke="tomato" />
    </pattern>

    <g stroke="royalBlue" fill="url(#stripes)">
      <rect x="0.1in" y="0.1in" width="3.8in" height="1in" />
      <ellipse cx="50%" cy="2.1in" rx="1.2in" ry="0.8in" />
    </g>
  </svg>

<pattern> 元素内部,你可以放置任何你想要重复的图案。然后通过与引用其他渲染服务一样的方式,通过url(#id)的方式来使用定义好的图案。

4. 动画

除了通过属性设置来操作XML的样式,也可以通过CSS配置选择器来设置SVG的样式。

SVG的文档对象模型DOM也可通过JavaScript进行操作,因此可以使用JS创建和重排元素,获取和设置属性的值,查询计算后的样式的值,实现酷炫的SVG动画。

给 SVG 图形添加动画的方法有三种:

  • 在标记内包含动画元素(<animate><set><animateTransform> 以及 <animateMotion>)来修改其他元素;
  • 在图形的样式中添加 CSS 动画或过渡属性;
  • 使用 JavaScript 依次操作图形的样式或属性。

手动编写完整的SVG动画是比较麻烦的,在实际业务中一般使用gsap等库来实现。

常见的SVG动画有:路径动画、描边动画等,这里不再展开了。

5. 小结

虽然目前在实际业务中经常会用到SVG(主要是图标),对于其语法还是有很多不了解的地方。

这本书并没有介绍SVG相关基础语法,而是深入讲解了绘制的原理,因此还是有一些不太明白的地方,在阅读过程中也查阅了不少的知识补充在笔记里面,之后再重新翻阅。