核心知识点
注意本文中的代码示例为了适应 Docusaurus 的代码实时预览,不得不把 SVG 代码转为 JSX 格式,所以请重点关注知识点,里面的样式或者注释的写法正常还是应该用 HTML 语法。
1. 什么是 SVG?
SVG (Scalable Vector Graphics) 是一种基于 XML 的、用于描述二维矢量图形的标记语言。与 PNG、JPG 等位图不同,SVG 图形是基于数学方程描述的,因此可以无限缩放而不失真,非常适合用于 Web 上的图标、Logo、图表和复杂插画。
核心优势:
- 可缩放性 (Scalability): 在任何分辨率下都能保持清晰度。
- 基于文本 (Text-based): 基于 XML,易于编辑、索引和搜索,对 SEO 友好。
- 可编程性 (Programmability): 可以通过 CSS 和 JavaScript 进行样式化和操作。
- 文件体积小 (Smaller File Size): 对于简单图形,通常比位图文件小。
- 交互性 (Interactivity): 支持事件处理和动画。
2. SVG 基础结构与坐标系
2.1 基本结构
一个 SVG 文件通常以 <svg>
根元素开始,包含图形元素、样式定义等。
<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg"> <rect x="10" y="10" width="80" height="80" fill="blue" /> </svg>
width
,height
: 定义 SVG 画布在 HTML 中的显示尺寸。xmlns
: 定义 SVG 的 XML 命名空间。
2.2 坐标系与 viewBox
SVG 使用一个二维笛卡尔坐标系。默认情况下:
- 原点 (0,0) 在画布的左上角。
- X 轴正方向向右。
- Y 轴正方向向下。
viewBox
属性是理解 SVG 缩放和坐标系的关键:
viewBox="min-x min-y width height"
: 定义了 SVG 画布上用户坐标系统的一个区域。浏览器会根据width
/height
属性和viewBox
的宽高比来缩放这个区域以适应 SVG 元素的尺寸。min-x
,min-y
:viewBox
左上角的坐标。width
,height
:viewBox
的宽度和高度(注意:这里的宽高是用户坐标单位,不是像素)。
通过 viewBox
,可以实现图形的平移和缩放效果,而无需修改图形元素本身的坐标。
<svg width="200" height="100" viewBox="0 0 400 200" style={{border: "1px solid black"}}> <rect x="50" y="50" width="200" height="100" fill="green" /> </svg>
- 这个矩形会占据 viewBox 的一半宽度和高度
- 由于 viewBox 的宽度是 400,而 SVG 元素宽度是 200,所有坐标和尺寸都被缩小了一半
3. 基本形状
SVG 提供了一系列预定义的形状元素,方便快速绘制常见图形。
3.1 矩形 <rect>
<svg width="100" height="100"> <rect x="10" y="10" width="80" height="80" fill="red" stroke="black" stroke-width="2" rx="5" ry="5" /> </svg>
- x, y: 左上角坐标
- width, height: 宽度和高度
- fill: 填充颜色
- stroke: 描边颜色
- stroke-width: 描边宽度
- rx, ry: 圆角半径
3.2 圆形 <circle>
<svg width="100" height="100"> <circle cx="50" cy="50" r="40" fill="yellow" stroke="orange" stroke-width="3" /> </svg>
- cx, cy: 圆心坐标
- r: 半径
3.3 椭圆 <ellipse>
<svg width="150" height="100"> <ellipse cx="75" cy="50" rx="60" ry="30" fill="purple" /> </svg>
- cx, cy: 圆心坐标
- rx, ry: 水平半径和垂直半径
3.4 直线 <line>
<svg width="100" height="100"> <line x1="10" y1="10" x2="90" y2="90" stroke="blue" stroke-width="4" /> </svg>
- x1, y1: 起点坐标
- x2, y2: 终点坐标
3.5 折线 <polyline>
由一系列直线段连接而成,但最后一个点不自动连接到第一个点。
<svg width="100" height="100"> <polyline points="10,10 90,10 50,90" fill="none" stroke="green" stroke-width="3" /> </svg>
- points: 一系列点的坐标 (x1,y1 x2,y2 x3,y3...)
3.6 多边形 <polygon>
与折线类似,但最后一个点会自动连接到第一个点,形成闭合形状。
<svg width="100" height="100"> <polygon points="50,10 90,90 10,90" fill="lightblue" stroke="darkblue" stroke-width="2" /> </svg>
- points: 一系列顶点的坐标 (x1,y1 x2,y2 x3,y3...)
4. 路径 <path>
<path>
是 SVG 中最强大、最灵活的绘图元素,可以绘制任何形状,包括直线、曲线、弧形等。其核心在于 d
属性,它包含一系列绘图指令。
常用指令 (区分大小写,大写表示绝对坐标,小写表示相对坐标):
M x y
/m dx dy
: Move To (移动到指定点,开始新子路径)L x y
/l dx dy
: Line To (绘制直线到指定点)H x
/h dx
: Horizontal Line To (绘制水平线到指定 x 坐标)V y
/v dy
: Vertical Line To (绘制垂直线到指定 y 坐标)C x1 y1, x2 y2, x y
/c dx1 dy1, dx2 dy2, dx dy
: Curve To (绘制三次贝塞尔曲线)(x1, y1)
: 起始控制点(x2, y2)
: 结束控制点(x, y)
: 终点
S x2 y2, x y
/s dx2 dy2, dx dy
: Smooth Curve To (绘制平滑的三次贝塞尔曲线,第一个控制点是前一个 C/S 命令的反射)Q x1 y1, x y
/q dx1 dy1, dx dy
: Quadratic Curve To (绘制二次贝塞尔曲线)(x1, y1)
: 控制点(x, y)
: 终点
T x y
/t dx dy
: Smooth Quadratic Curve To (绘制平滑的二次贝塞尔曲线,控制点是前一个 Q/T 命令的反射)A rx ry x-axis-rotation large-arc-flag sweep-flag x y
/a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
: Arc To (绘制椭圆弧)rx, ry
: 椭圆半径x-axis-rotation
: 椭圆旋转角度large-arc-flag
: 0 表示小弧,1 表示大弧sweep-flag
: 0 表示逆时针,1 表示顺时针(x, y)
/(dx, dy)
: 终点
Z
/z
: Close Path (闭合当前路径,绘制一条直线回到起点)
<svg width="200" height="100"> <path d="M 10 80 Q 50 10, 90 80 T 170 80" fill="none" stroke="purple" stroke-width="3"/> <path d="M 10 10 L 90 10 L 50 90 Z" fill="lightgreen" stroke="black" /> </svg>
- 有 Z 的会闭合路径,没有 Z 的不会闭合路径
5. 文本 <text>
与 <tspan>
SVG 允许直接在图形中嵌入文本。
<svg width="200" height="100"> <text x="10" y="40" font-family="Arial" font-size="20" fill="blue"> Hello SVG! </text> <text x="10" y="80" font-family="Verdana" font-size="16" fill="red"> <tspan font-weight="bold">Styling</tspan> <tspan dx="5" dy="-5" fill="green"> part</tspan> of text. </text> </svg>
- x, y: 文本基线的起始点坐标
- dx, dy: 相对于前一个字符的偏移量
- text-anchor: 文本对齐方式 (start, middle, end)
- tspan: 用于对文本片段应用不同的样式或定位
6. 样式化 SVG (Styling)
SVG 元素可以通过多种方式设置样式:
6.1 内联样式 (Presentation Attributes)
直接在元素上使用属性设置样式,如 fill
, stroke
, stroke-width
等。这是最简单的方式。
<circle cx="50" cy="50" r="40" fill="red" stroke="black" stroke-width="2"/>
6.2 CSS 样式
可以使用内部 CSS(<style>
标签)或外部 CSS 文件来为 SVG 元素定义样式,就像 HTML 一样。这种方式更易于管理和维护,特别是对于复杂的 SVG。
<svg width="100" height="100"> <defs> <style> {` .myCircle { fill: blue; stroke: navy; stroke-width: 3px; /* 单位可选 */ } #myRect { fill: orange; opacity: 0.8; } `} </style> </defs> <circle class="myCircle" cx="50" cy="30" r="25" /> <rect id="myRect" x="10" y="60" width="80" height="30" /> </svg>
常用样式属性:
fill
: 填充颜色或图案 (none
,<color>
,url(#gradientId)
,url(#patternId)
)fill-opacity
: 填充不透明度 (0-1)stroke
: 描边颜色或图案stroke-width
: 描边宽度stroke-opacity
: 描边不透明度stroke-linecap
: 线条末端样式 (butt
,round
,square
)stroke-linejoin
: 线条连接处样式 (miter
,round
,bevel
)stroke-dasharray
: 虚线模式 (e.g., "5, 5" 表示 5 单位实线,5 单位空白)stroke-dashoffset
: 虚线起始偏移量opacity
: 元素整体不透明度visibility
: 元素可见性 (visible
,hidden
)font-family
,font-size
,font-weight
,text-anchor
(用于<text>
)
注意: CSS 样式比内联样式具有更高的优先级。
7. 分组与复用
7.1 分组 <g>
<g>
元素用于将多个元素组合在一起。应用到 <g>
上的变换(transform
)和样式会传递给其所有子元素。
<svg width="150" height="100"> <g fill="green" stroke="black" stroke-width="2" transform="translate(10, 10) rotate(15)"> <rect x="0" y="0" width="50" height="30"/> <circle cx="70" cy="15" r="15"/> </g> </svg>
7.2 定义 <defs>
与复用 <use>
<defs>
元素用于定义可重用的图形对象(如渐变、图案、形状、滤镜等),这些定义本身不会被渲染。<use>
元素则可以引用 <defs>
中定义的元素,并在指定位置实例化(渲染)它们。
<svg width="200" height="100"> <defs> <rect id="reusableRect" width="50" height="30" fill="purple" /> <g id="reusableGroup"> <circle cx="15" cy="15" r="10" fill="orange" /> <circle cx="45" cy="15" r="10" fill="red" /> </g> </defs> {/* 使用定义的矩形 */} <use href="#reusableRect" x="10" y="10" /> <use href="#reusableRect" x="80" y="10" fill="lightblue" /> {/* 这样不可以覆盖样式,需要去掉 defs 里的属性定义才可以 */} {/* 使用定义的组 */} <use href="#reusableGroup" x="10" y="50" /> <use href="#reusableGroup" x="100" y="50" transform="scale(0.8)" /> </svg>
7.3 <symbol>
<symbol>
类似于 <g>
,通常放在 <defs>
中定义。与 <g>
不同的是,<symbol>
可以拥有自己的 viewBox
和 preserveAspectRatio
属性,使其在通过 <use>
引用时表现更像一个独立的 SVG 图标。
<svg width="100" height="50" style={{ border: "1px solid grey" }}> <defs> <symbol id="myIcon" viewBox="0 0 20 20"> <circle cx="10" cy="10" r="8" stroke="black" fill="none" /> <path d="M 5 10 L 15 10 M 10 5 L 10 15" stroke="red" /> </symbol> </defs> <use href="#myIcon" x="10" y="10" width="30" height="30" /> <use href="#myIcon" x="50" y="10" width="40" height="30" /> </svg>
- 使用 symbol, 它会自动缩放以适应
<use>
的 width/height- preserveAspectRatio 会生效
8. 坐标变换 transform
transform
属性允许对元素或组进行平移、旋转、缩放和倾斜,而不改变其原始定义。
可用函数:
translate(tx [ty])
: 平移。ty
省略则为 0。scale(sx [sy])
: 缩放。sy
省略则等于sx
。rotate(angle [cx cy])
: 旋转。angle
单位是度。cx, cy
是可选的旋转中心,默认为 (0,0)。skewX(angle)
: 沿 X 轴倾斜。skewY(angle)
: 沿 Y 轴倾斜。matrix(a b c d e f)
: 使用变换矩阵直接定义变换。
多个变换函数可以按顺序应用,写在同一个 transform
属性中,用空格分隔。变换是从右向左依次应用的。
<svg width="200" height="200"> <rect x="0" y="0" width="50" height="50" fill="blue" opacity="0.5" /> {/* 先旋转 45 度,再平移 (100, 50) */} <rect x="0" y="0" width="50" height="50" fill="red" transform="translate(100, 50) rotate(45)" /> {/* 另一种写法:旋转中心在矩形中心 (25, 25) */} <rect x="0" y="0" width="50" height="50" fill="green" transform="translate(50, 120) rotate(30, 25, 25) scale(1.2)" /> </svg>
9. 填充与描边高级
9.1 渐变 (Gradients)
渐变在 <defs>
中定义,通过 fill
或 stroke
属性引用 url(#gradientId)
。
- 线性渐变
<linearGradient>
:id
: 唯一标识符。x1
,y1
,x2
,y2
: 定义渐变线的起点和终点(默认值 "0%", "0%", "100%", "0%" - 从左到右)。gradientUnits
: 坐标系统 (userSpaceOnUse
或objectBoundingBox
- 默认值)。<stop>
: 定义渐变中的颜色点。offset
: 颜色点的位置 (0% 到 100% 或 0 到 1)。stop-color
: 该点的颜色。stop-opacity
: 该点的透明度 (可选)。
<svg width="150" height="100"> <defs> <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" style={{ stopColor: "rgb(255,255,0)", stopOpacity: 1 }} /> <stop offset="100%" style={{ stopColor: "rgb(255,0,0)", stopOpacity: 1 }} /> </linearGradient> </defs> <rect x="10" y="10" width="130" height="80" fill="url(#grad1)" /> </svg>
- 径向渐变
<radialGradient>
:id
: 唯一标识符。cx
,cy
,r
: 定义最外层圆的圆心和半径(默认 "50%", "50%", "50%")。fx
,fy
: 定义焦点(最内层颜色)的位置(默认等于cx
,cy
)。gradientUnits
: 同上。<stop>
: 同上。
<svg width="150" height="150"> <defs> <radialGradient id="grad2" cx="50%" cy="50%" r="50%" fx="70%" fy="70%"> <stop offset="0%" style={{ stopColor: "white", stopOpacity: 0 }} /> <stop offset="100%" style={{ stopColor: "blue", stopOpacity: 1 }} /> </radialGradient> </defs> <ellipse cx="75" cy="75" rx="60" ry="60" fill="url(#grad2)" /> </svg>
9.2 图案 <pattern>
允许使用一个图形或一组图形作为填充或描边。在 <defs>
中定义。
id
: 唯一标识符。x
,y
,width
,height
: 定义图案单元(tile)的尺寸和位置。patternUnits
: 定义x
,y
,width
,height
的坐标系 (userSpaceOnUse
或objectBoundingBox
- 默认)。patternContentUnits
: 定义图案内容本身的坐标系 (userSpaceOnUse
- 默认 或objectBoundingBox
)。
<svg width="200" height="200"> <defs> <pattern id="patt1" width="20" height="20" patternUnits="userSpaceOnUse"> <circle cx="10" cy="10" r="8" fill="none" stroke="blue"/> </pattern> <pattern id="patt2" width="0.2" height="0.2" patternUnits="objectBoundingBox"> <rect x="0" y="0" width="0.1" height="0.1" fill="red"/> <rect x="0.1" y="0.1" width="0.1" height="0.1" fill="green"/> </pattern> </defs> <rect x="10" y="10" width="180" height="80" fill="url(#patt1)" stroke="black"/> <ellipse cx="100" cy="150" rx="80" ry="40" fill="url(#patt2)" stroke="black"/> </svg>
10. 裁剪 <clipPath>
与蒙版 <mask>
10.1 裁剪 <clipPath>
定义一个裁剪区域,只有区域内的部分才会被渲染。<clipPath>
元素内的任何图形(包括基本形状、路径、文本)都定义了可见区域。在 <defs>
中定义,通过元素的 clip-path
属性引用。
<svg width="150" height="150"> <defs> <clipPath id="clip1"> <circle cx="75" cy="75" r="50" /> {/* 只有圆内的部分可见 */} </clipPath> </defs> {/* 一个渐变矩形 */} <rect x="0" y="0" width="150" height="150" fill="url(#grad1)" /> {/* 应用裁剪路径的文本 */} <text x="50%" y="50%" dy=".3em" textAnchor="middle" fontSize="40" fontWeight="bold" fill="black" clipPath="url(#clip1)" > CLIP </text> {/* 定义渐变以供上面矩形使用 */} <defs> <linearGradient id="grad1"> <stop offset="0%" stopColor="gold" /> <stop offset="100%" stopColor="red" /> </linearGradient> </defs> </svg>
10.2 蒙版 <mask>
提供比裁剪更复杂的可见性控制。蒙版内的图形的灰度(亮度)或透明度决定了被蒙版元素相应位置的透明度。通常白色表示完全不透明,黑色表示完全透明。在 <defs>
中定义,通过元素的 mask
属性引用。
<svg width="150" height="150"> <defs> {/* 定义一个从白到黑的线性渐变作为蒙版内容 */} <linearGradient id="maskGrad" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stopColor="white" /> <stop offset="100%" stopColor="black" /> </linearGradient> {/* 定义蒙版,使用上面的渐变填充一个矩形 */} <mask id="mask1"> <rect x="0" y="0" width="150" height="150" fill="url(#maskGrad)" /> </mask> </defs> {/* 一个红色的圆 */} <circle cx="75" cy="75" r="70" fill="red" /> {/* 应用蒙版的蓝色矩形,左侧(白色蒙版区域)完全可见,向右逐渐透明 */} <rect x="0" y="0" width="150" height="150" fill="blue" mask="url(#mask1)" /> </svg>
11. 滤镜效果 <filter>
SVG 滤镜允许对图形应用各种像素级别的效果,如模糊、阴影、颜色变换等。滤镜在 <defs>
中定义,包含一系列滤镜基元 (filter primitives),这些基元按顺序处理输入图形。通过元素的 filter
属性引用。
常用滤镜基元:
feGaussianBlur
: 高斯模糊 (stdDeviation
属性控制模糊程度)feOffset
: 位移 (dx
,dy
属性控制偏移量)feMerge
: 合并多个输入(如图形本身和偏移后的模糊阴影)feMergeNode
: 指定要合并的输入 (in
属性,如SourceGraphic
,SourceAlpha
, 或其他基元的result
)
feColorMatrix
: 颜色变换(用于饱和度、色相旋转、亮度转 alpha 等)feBlend
: 混合模式feImage
: 引入外部图像或引用 SVG 其他部分feTurbulence
: 生成柏林噪音(用于云、水纹理)feDisplacementMap
: 使用另一个图像的像素值来扭曲当前图像
<svg width="200" height="150"> <defs> <filter id="dropShadow"> {/* 创建阴影: 模糊原始图形的 Alpha 通道 */} <feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur" /> {/* 偏移模糊后的 Alpha 通道 */} <feOffset in="blur" dx="3" dy="3" result="offsetBlur" /> {/* 合并原始图形和阴影 */} <feMerge> <feMergeNode in="offsetBlur" /> {/* 阴影在下层 */} <feMergeNode in="SourceGraphic" /> {/* 原始图形在上层 */} </feMerge> </filter> </defs> {/* 应用滤镜的矩形 */} <rect x="20" y="20" width="100" height="60" fill="lightblue" filter="url(#dropShadow)" /> {/* 应用滤镜的文本 */} <text x="30" y="120" fontSize="24" filter="url(#dropShadow)"> Shadow </text> </svg>
12. 交互与动画
SVG 支持多种方式实现交互和动画:
12.1 CSS (Transitions & Animations)
SVG 元素的样式属性(如 fill
, stroke
, transform
, opacity
等)可以通过 CSS 的 transition
和 animation
(@keyframes
) 来实现动画效果。这是目前推荐的主流方式之一。
<svg width="100" height="100"> <style>{` .animCircle { fill: blue; transition: fill 0.5s ease, r 0.5s ease; /* 过渡效果:注意 r 过渡通常无效 */ } .animCircle:hover { fill: red; r: 45; /* 悬停时变色变大,这里的 r 属性在 CSS 中不生效 */ } @keyframes pulse { /* 关键帧动画 */ 0% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.1); } 100% { opacity: 1; transform: scale(1); } } .pulsingRect { fill: green; animation: pulse 2s infinite; /* 应用动画 */ } `}</style> <circle className="animCircle" cx="50" cy="30" r="25" /> <rect className="pulsingRect" x="25" y="65" width="50" height="20" /> </svg>
12.2 JavaScript
可以通过标准的 DOM API 或库(如 GreenSock GSAP, D3.js, Snap.svg)来操作 SVG 元素的属性和样式,实现复杂的交互和动画逻辑。
// 假设页面中有一个 id="myCircle" 的 SVG 圆形
const circle = document.getElementById("myCircle");
if (circle) {
circle.addEventListener("click", () => {
const currentFill = circle.getAttribute("fill");
circle.setAttribute("fill", currentFill === "blue" ? "orange" : "blue");
});
// 使用 JS 动画库 (示例概念, 需要引入库)
// gsap.to("#myCircle", { duration: 1, x: 100, rotation: 360, ease: "power2.out" });
}
12.3 SMIL (Synchronized Multimedia Integration Language) - 不推荐
SVG 内建了一套基于 XML 的动画规范 (SMIL),允许直接在 SVG 文件中定义动画(如 <animate>
, <animateTransform>
, <animateMotion>
)。虽然功能强大,但由于浏览器支持不一致(特别是 IE 和部分 Edge 版本不支持)以及 CSS 和 JS 动画的兴起,SMIL 已不再被广泛推荐使用。
<svg width="100" height="100"> <rect x="10" y="10" width="20" height="20" fill="purple"> <animate attributeName="x" from="10" to="70" dur="2s" repeatCount="indefinite" /> </rect> </svg>
13. SVG 优化
为了提高性能和加载速度,可以对 SVG 进行优化:
- 简化路径: 减少
<path>
中的锚点数量,移除不必要的精度。 - 合并路径: 将多个路径合并成一个(如果样式相同)。
- 使用
<use>
: 对于重复的元素,使用<defs>
或<symbol>
定义,然后通过<use>
引用。 - 移除冗余信息: 删除编辑器生成的元数据、注释、隐藏图层等。
- 优化工具: 使用 SVGO (SVG Optimizer) 等工具自动化优化过程。
- 嵌入方式: 对于小图标,考虑内联 SVG 或 SVG Sprite,减少 HTTP 请求。对于大图,作为
.svg
文件引用。 - Gzip 压缩: 在服务器端启用 Gzip 压缩,因为 SVG 是基于文本的。
14. 应用场景
SVG 因其独特的优势,在现代 Web 开发中应用广泛:
- 图标系统: Font Awesome, Material Icons 等都提供 SVG 版本。
- Logo: 清晰、可缩放的品牌标识。
- 数据可视化: 图表、地图 (D3.js 等库大量使用 SVG)。
- 复杂插画和信息图: 需要细节和缩放能力的图形。
- UI 元素: 特殊形状的按钮、加载指示器、分割线等。
- Web 动画: 结合 CSS 或 JS 实现动态效果。
- 网页背景: 可缩放的矢量背景图案。
15. 总结
SVG 是一种强大且灵活的 Web 图形格式。掌握其核心概念,如坐标系、基本形状、路径、样式化、变换、复用机制以及与 CSS/JS 的结合,对于现代前端开发者至关重要。合理运用 SVG 不仅能提升图形质量和用户体验,还能优化性能和开发效率。