前置知识: HTML5

SVG

6 min中级

基本形状、viewBox、路径、文本、滤镜

前置知识

建议先阅读以下内容再进入本文:

前置要求:SVG 画图本身零基础可学(形状、路径、渐变都不需要 JS);涉及交互的示例需要 JavaScript 基础(javascript/001-005、039)。本章末尾的”SVG 与 Canvas 互转”需要先了解 006 的 Canvas 概念,第一遍可跳过。

1. SVG 基础

html
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="100" r="80" fill="blue" />
</svg>

2. viewBox 详解

viewBox="min-x min-y width height" 定义 SVG 的内部坐标系统:

html
<svg width="400" height="300" viewBox="0 0 200 150">
  <rect x="0" y="0" width="100" height="75" fill="blue" />
</svg>
preserveAspectRatio 值说明
xMidYMid meet居中,完整显示(默认)
xMidYMid slice居中,裁剪填充
none不保持比例

3. 基本形状

html
<svg width="400" height="300">
  <rect x="10" y="10" width="100" height="60" rx="10" fill="blue" />
  <circle cx="200" cy="80" r="50" fill="red" />
  <ellipse cx="320" cy="80" rx="60" ry="30" fill="green" />
  <line x1="10" y1="150" x2="390" y2="150" stroke="black" />
  <polyline points="10,180 50,160 90,200" fill="none" stroke="purple" />
  <polygon points="200,180 240,220 160,220" fill="orange" />
</svg>

4. 路径 path

命令说明示例
M移动到M 10 10
L直线到L 100 100
C三次贝塞尔C 20,20 40,20 50,10
Q二次贝塞尔Q 50,0 100,50
A弧线A 25,25 0 0,1 50,25
Z闭合路径Z

小写字母为相对坐标,大写字母为绝对坐标。

5. 文本

html
<svg>
  <text x="20" y="50" font-size="24" fill="black">Hello SVG</text>
  <defs><path id="curve" d="M 50 150 Q 200 50, 350 150" /></defs>
  <text font-size="20"><textPath href="#curve">沿曲线排列的文字</textPath></text>
</svg>

6. 滤镜与渐变

html
<svg>
  <defs>
    <filter id="blur"><feGaussianBlur stdDeviation="5" /></filter>
    <filter id="shadow"><feDropShadow dx="4" dy="4" stdDeviation="3" /></filter>
    <linearGradient id="lg" x1="0%" x2="100%">
      <stop offset="0%" stop-color="red" />
      <stop offset="100%" stop-color="blue" />
    </linearGradient>
  </defs>
  <rect x="50" y="50" width="100" height="80" fill="url(#lg)" filter="url(#shadow)" />
</svg>

7. SVG 与 Canvas:对比与互转

7.1 什么时候选 SVG

SVG 与 Canvas 是两套互补的绘图体系,先看差异再选型:

特性CanvasSVG
绘图方式基于像素,通过 JavaScript 绘制基于矢量,使用 XML 标记
缩放缩放会失真缩放不失真
性能适合绘制大量图形和动画适合绘制少量静态图形
事件处理需要手动实现支持元素级事件
适用场景游戏、复杂动画、数据可视化图标、图表、标志

判断口诀:图形少、要交互、要任意缩放选 SVG;图形多、逐帧动画、要像素级操作选 Canvas。完整的 Canvas 侧讲解见 235-Canvas2DDrawing。

7.2 SVG 画到 Canvas(导出、加水印、合成)

实际项目偶尔需要”把 SVG 画到 Canvas 上”(例如做导出、加水印、截图合成)。原理是把 SVG 序列化成图片再画进 Canvas:

javascript
async function drawSvgToCanvas(svg, canvas) {
  const xml = new XMLSerializer().serializeToString(svg);
  const svgBlob = new Blob([xml], { type: 'image/svg+xml;charset=utf-8' });
  const url = URL.createObjectURL(svgBlob);

  const img = new Image();
  await new Promise((resolve, reject) => {
    img.onload = resolve;
    img.onerror = reject;
    img.src = url;
  });

  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  URL.revokeObjectURL(url); // 释放临时 URL
}

// 使用:把页面上 id 为 mySvg 的 SVG 画进 canvas
drawSvgToCanvas(document.getElementById('mySvg'), document.getElementById('myCanvas'));

讲解:

  1. XMLSerializer 把 SVG DOM 节点序列化成 XML 字符串。
  2. Blob + URL.createObjectURL 生成一个临时图片地址,Image 加载它。
  3. ctx.drawImage 把图片按画布尺寸绘制;SVG 是矢量,绘制到任意尺寸都不糊。
  4. revokeObjectURL 释放临时 URL,防止内存泄漏。
  5. 反向操作(Canvas → SVG)没有官方 API,通常是把 Canvas 导出为 PNG 再嵌入 <image>,或直接用 canvas.toDataURL() 生成位图。

第一遍可跳过:本节需要 JavaScript 异步基础(Promise),读完 javascript/023 再回来。

SVG 基础

svg 元素 <svg [width] [height] [viewBox="<min-x> <min-y> <width> <height>"] [xmlns="http://www.w3.org/2000/svg"]>...</svg>

html
<!-- 基础 SVG -->
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
  <circle cx="100" cy="100" r="80" fill="blue" />
</svg>

<!-- 内联 SVG(HTML 中可直接使用) -->
<svg width="100" height="100">
  <rect width="100" height="100" fill="red" />
</svg>

viewBox 坐标系统

viewBox 详解 viewBox="<min-x> <min-y> <width> <height>"

html
<!-- 200x150 内部坐标,显示为 400x300 -->
<svg width="400" height="300" viewBox="0 0 200 150">
  <rect x="0" y="0" width="100" height="75" fill="blue" />
</svg>

preserveAspectRatio 属性 preserveAspectRatio="[alignment] [meet|slice|none]"

html
<svg width="400" height="300" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid meet">
  <rect width="200" height="150" fill="green" />
</svg>
preserveAspectRatio 值说明
xMidYMid meet居中,完整显示(默认)
xMidYMid slice居中,裁剪填充
xMinYMin meet左上对齐,完整显示
none不保持比例,拉伸填充

基本形状

矩形 rect <rect x="<X>" y="<Y>" width="<宽>" height="<高>" [rx="<圆角X>"] [ry="<圆角Y>"] [fill] [stroke] [stroke-width] />

html
<rect x="10" y="10" width="100" height="60" rx="10" ry="10" fill="blue" stroke="black" stroke-width="2" />

圆形 circle <circle cx="<圆心X>" cy="<圆心Y>" r="<半径>" [fill] [stroke] />

html
<circle cx="200" cy="80" r="50" fill="red" />

椭圆 ellipse <ellipse cx="<圆心X>" cy="<圆心Y>" rx="<X半径>" ry="<Y半径>" [fill] [stroke] />

html
<ellipse cx="320" cy="80" rx="60" ry="30" fill="green" />

直线 line <line x1="<起点X>" y1="<起点Y>" x2="<终点X>" y2="<终点Y>" stroke="<颜色>" [stroke-width] />

html
<line x1="10" y1="150" x2="390" y2="150" stroke="black" stroke-width="2" />

折线 polyline <polyline points="<x1>,<y1> <x2>,<y2> ..." [fill] [stroke] />

html
<polyline points="10,180 50,160 90,200 130,170" fill="none" stroke="purple" stroke-width="2" />

多边形 polygon <polygon points="<x1>,<y1> <x2>,<y2> ..." [fill] [stroke] />

html
<polygon points="200,180 240,220 160,220" fill="orange" stroke="black" />

路径 path

path 元素 <path d="<路径命令>" [fill] [stroke] [stroke-width] />

html
<!-- 三角形 -->
<path d="M 100 100 L 200 100 L 150 50 Z" fill="yellow" stroke="black" />

<!-- 心形 -->
<path d="M 100 200 C 50 100, 0 200, 100 300 C 200 200, 150 100, 100 200 Z" fill="red" />

路径命令

命令说明示例
M移动到(绝对)M 10 10
m移动到(相对)m 10 10
L直线到(绝对)L 100 100
l直线到(相对)l 10 10
H水平线到H 100
V垂直线到V 100
C三次贝塞尔C 20,20 40,20 50,10
S平滑三次贝塞尔S 40,20 50,10
Q二次贝塞尔Q 50,0 100,50
T平滑二次贝塞尔T 100,50
A弧线A 25,25 0 0,1 50,25
Z闭合路径Z

小写字母为相对坐标,大写字母为绝对坐标。

A 弧线参数 A rx ry x-axis-rotation large-arc-flag sweep-flag x y

html
<!-- 半圆弧 -->
<path d="M 50 100 A 50 50 0 0 1 150 100" stroke="red" fill="none" />

文本

text 元素 <text x="<X>" y="<Y>" [font-size] [font-family] [fill] [text-anchor] [dominant-baseline]>[文本]</text>

html
<text x="20" y="50" font-size="24" font-family="Arial" fill="black"
      text-anchor="start" dominant-baseline="alphabetic">
  Hello SVG
</text>
text-anchor 值对齐方式
start左对齐
middle居中
end右对齐

tspan 子文本

html
<text x="10" y="50">
  <tspan font-weight="bold">Hello</tspan>
  <tspan fill="red">World</tspan>
</text>

textPath 沿路径排版

html
<defs>
  <path id="curve" d="M 50 150 Q 200 50, 350 150" />
</defs>
<text font-size="20" fill="blue">
  <textPath href="#curve">沿曲线排列的文字</textPath>
</text>

渐变与滤镜

线性渐变

html
<defs>
  <linearGradient id="lg" x1="0%" y1="0%" x2="100%" y2="0%">
    <stop offset="0%" stop-color="red" />
    <stop offset="50%" stop-color="yellow" />
    <stop offset="100%" stop-color="blue" />
  </linearGradient>
</defs>
<rect x="50" y="50" width="100" height="80" fill="url(#lg)" />

径向渐变

html
<defs>
  <radialGradient id="rg" cx="50%" cy="50%" r="50%">
    <stop offset="0%" stop-color="white" />
    <stop offset="100%" stop-color="black" />
  </radialGradient>
</defs>
<circle cx="100" cy="100" r="80" fill="url(#rg)" />

滤镜

html
<defs>
  <!-- 高斯模糊 -->
  <filter id="blur">
    <feGaussianBlur stdDeviation="5" />
  </filter>

  <!-- 阴影 -->
  <filter id="shadow">
    <feDropShadow dx="4" dy="4" stdDeviation="3" flood-color="black" flood-opacity="0.5" />
  </filter>
</defs>

<rect x="50" y="50" width="100" height="80" fill="blue" filter="url(#shadow)" />

g 分组与 use 引用

g 分组 <g [transform] [fill] [stroke] [opacity]>...</g>

html
<g transform="translate(50, 50)" fill="red" stroke="black">
  <rect width="50" height="50" />
  <circle cx="80" cy="25" r="25" />
</g>

defs 与 use

html
<defs>
  <symbol id="icon" viewBox="0 0 24 24">
    <circle cx="12" cy="12" r="10" />
  </symbol>
</defs>

<!-- 多次引用 -->
<use href="#icon" x="0" y="0" width="50" height="50" />
<use href="#icon" x="60" y="0" width="50" height="50" fill="red" />

SVG 属性参考

通用样式属性

属性作用示例
fill填充颜色fill="red"
fill-opacity填充透明度fill-opacity="0.5"
stroke描边颜色stroke="black"
stroke-width描边宽度stroke-width="2"
stroke-opacity描边透明度stroke-opacity="0.8"
stroke-linecap线帽 butt/round/squarestroke-linecap="round"
stroke-linejoin连接 miter/round/bevelstroke-linejoin="round"
stroke-dasharray虚线stroke-dasharray="5,5"
opacity整体透明度opacity="0.8"
transform变换transform="rotate(45)"

transform 变换

html
<!-- translate/scale/rotate/skew -->
<g transform="translate(50,50) rotate(45) scale(1.5)">
  <rect width="100" height="100" />
</g>

SVG 动画

SMIL 动画(SVG 原生)

html
<rect x="0" y="0" width="50" height="50" fill="red">
  <animate attributeName="x" from="0" to="200" dur="2s" repeatCount="indefinite" />
</rect>

<circle cx="50" cy="50" r="20" fill="blue">
  <animate attributeName="r" values="20;40;20" dur="2s" repeatCount="indefinite" />
</circle>

CSS 动画

html
<style>
  .box {
    animation: move 2s infinite alternate;
  }
  @keyframes move {
    from { transform: translateX(0); }
    to { transform: translateX(100px); }
  }
</style>

<svg>
  <rect class="box" width="50" height="50" fill="red" />
</svg>

SVG 在 HTML 中的使用

直接内联

html
<svg width="50" height="50">
  <circle cx="25" cy="25" r="20" fill="red" />
</svg>

img 引用

html
<img src="icon.svg" alt="图标" width="50" height="50" />

CSS 背景

css
.icon {
  background: url('icon.svg') no-repeat center;
  width: 50px;
  height: 50px;
}

object 引用

html
<object data="icon.svg" type="image/svg+xml" width="50" height="50"></object>

图标实战:一套 stroke 图标的通用写法

工程里最常见的 SVG 用途是图标。一套统一风格的图标通常有固定模板:viewBox="0 0 24 24" 24 格坐标系、fill="none" 只留描边、stroke="currentColor" 跟随文字颜色:

html
<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  width="24"
  height="24"
>
  <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
  <circle cx="12" cy="10" r="3"></circle>
</svg>

逐段讲解:

  • viewBox="0 0 24 24" 定义 24x24 的内部坐标系,外部通过 width/height 任意缩放——这是”一个图标文件到处可用”的关键;
  • stroke="currentColor" 让描边颜色继承 CSS 的 color,按钮文字变灰图标跟着变灰,无需复制多色版本;
  • stroke-linecap="round" 与 stroke-linejoin="round" 把线段端点与拐角变圆,是现代图标风格的标配;
  • 换图标只需替换内部 <path>/<circle>,外壳模板不变;同类图标可收进 <symbol> + <use> 复用(见上文 g 分组与 use 引用节)。

SVG 图标最佳实践清单:

  • 使用 viewBox 让图标自适应任意尺寸;
  • 简化路径节点数量,提高渲染性能;
  • 样式交给 CSS(fill/stroke 可被 CSS 覆盖),不要写死在属性里;
  • 重复使用的图形用 <symbol> + <use>,减少代码冗余;
  • 小图标直接内联到 HTML,减少一次 HTTP 请求,还能被 CSS 改色;
  • 上线前用 SVGO 之类工具压缩 SVG,减小体积。

动手试试

入门版(必做)

  1. 用 <circle> 和 <rect> 画一个简单的“太阳和房子”;
  2. 给房子加一个 viewBox="0 0 100 100",观察不同 preserveAspectRatio 的效果;
  3. 用 <text> 在图形下方加一行说明文字。

进阶版(选做)

  1. 用 <symbol> + <use> 做一个 3 个相同图标的工具栏,第二个改成红色;
  2. 用 <animate> 让圆点沿水平方向来回移动;
  3. 用 CSS 给内联 SVG 加悬停变色效果。

核心知识点

一句话记住 SVG:矢量图形用代码画,放大不糊;svg 是画布,g 分组,use 复用;viewBox 定坐标,path 画任意形状。

  • SVG 是矢量格式:无损缩放、可被 CSS/JS 操作,适合图标与图表;
  • viewBox 定义内部坐标系,preserveAspectRatio 控制对齐;
  • 基本形状:rect/circle/ellipse/line/polyline/polygon;
  • path 用 M/L/C/A/Z 等命令绘制任意路径;
  • defs + symbol + use 实现图标复用,g 实现分组;
  • 动画可用 SMIL <animate> 或 CSS。

注意事项与改进建议

问题点说明改进方案
图片用位图硬放放大模糊、体积大图标/Logo 改用 SVG
忘记 viewBox坐标混乱、缩放变形统一用 viewBox 定义坐标系
手写复杂 path易错且难以维护用设计工具导出,或复用图标库
<img> 引用 SVG无法用 CSS 改色需要改色时内联 SVG
滤镜滥用渲染性能下降滤镜只用于小范围图形
缺少 xmlns部分环境无法识别根 <svg> 保留 xmlns 声明

扩展学习

  • 坐标系:svg/030-SVGCoordinateSystemViewBox 深入 viewBox 与坐标变换;
  • 动画:css/330-CSSAnimationTransition 对比 CSS 动画与 SMIL;
  • 绘图选型:html5/235-Canvas2DDrawing 中 Canvas 与 SVG 选型对比与动画循环。