内联 SVG
一、SVG 概述
1.1 什么是 SVG
SVG(可缩放矢量图形)是一种基于 XML 的矢量图形格式,用于描述二维矢量图形。与位图(如 PNG、JPG)不同,SVG 图形可以无限缩放而不失真,文件体积通常更小,且可以直接在浏览器中编辑和操作。
<!-- 内联 SVG 示例 -->
<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" fill="plum" />
<rect x="100" y="20" width="80" height="60" fill="lightblue" />
</svg>
1.2 内联 SVG vs 外部引用
| 方式 | 优点 | 缺点 |
|---|---|---|
<img src="*.svg"> |
浏览器缓存、独立复用 | 不可 CSS 样式化 |
<object data="*.svg"> |
可交互、可添加后备 | 需要插件渲染 |
内联 <svg> |
可 DOM 操作、CSS 样式化、无请求 | 增大 HTML 体积 |
background-image: url(*.svg) |
可 CSS 样式化、浏览器缓存 | 不可 DOM 操作 |
二、svg 元素详解
2.1 视口与 viewBox
viewBox 属性定义了 SVG 的用户坐标系,使得 SVG 内部元素可以使用相对单位而非绝对像素值:
<!-- viewBox="minX minY width height" -->
<svg viewBox="0 0 300 100" width="600" height="200">
<!-- 圆形直径为 40(相对于 300 宽度的单位)-->
<circle cx="50" cy="50" r="20" fill="coral" />
</svg>
2.2 xmlns 属性
<!-- 最外层 svg 在 HTML 中通常不需要 xmlns -->
<!-- HTML5 解析器会自动识别为 SVG 命名空间 -->
<!-- ✅ HTML 中的内联 SVG -->
<svg viewBox="0 0 100 100">
<circle r="10" />
</svg>
<!-- ⚠️ 在 XHTML 或需要 XML 序列化时需要 xmlns -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle r="10" />
</svg>
2.3 SVG2 几何属性
SVG2 将一些几何属性引入为 CSS 属性:
<svg viewBox="0 0 200 200">
<!-- 作为 HTML 属性 -->
<rect x="10" y="10" width="100" height="50" fill="tomato" />
<!-- 作为 CSS 属性(SVG2 支持)-->
<rect x="120" y="10" width="100" height="50" style="fill: steelblue" />
</svg>
/* CSS 中使用 SVG 属性 */
.svg-shape {
fill: #3498db;
stroke: #2c3e50;
stroke-width: 2;
}
三、CSS 样式化
3.1 可样式化的 SVG 属性
内联 SVG 可以使用 CSS 进行样式化,但需要注意哪些属性可以 CSS 化:
/* ✅ 可 CSS 化的属性 */
.svg-text {
fill: #333; /* 填充色 */
stroke: #666; /* 描边色 */
stroke-width: 2px; /* 描边宽度 */
opacity: 0.8; /* 透明度 */
fill-opacity: 0.5; /* 填充透明度 */
}
/* ⚠️ 部分可样式化(需要 SVG CSS 属性)*/
.transformed {
transform: rotate(45deg);
}
/* ❌ 不可 CSS 化的几何属性 */
.invalid {
/* 这些必须在 SVG 属性或 CSS 属性中设置 */
cx: 50px; /* ❌ 无效 */
r: 20px; /* ❌ 无效 */
}
3.2 使用 CSS 变量
<svg viewBox="0 0 100 100" class="icon">
<circle r="40" class="icon-bg" />
</svg>
.icon {
--icon-color: currentColor;
--icon-bg: transparent;
}
.icon .icon-bg {
fill: var(--icon-bg);
}
.theme-dark {
--icon-bg: white;
}
3.3 继承与层叠
<!-- SVG 元素会从父元素继承一些 CSS 属性 -->
<div style="color: blue;">
<svg viewBox="0 0 100 100" width="100" height="100">
<!-- text 会继承父元素的 color -->
<text y="50" fill="currentColor">Text</text>
<!-- circle 不会继承 fill -->
<circle cy="70" r="10" fill="currentColor" />
</svg>
</div>
四、DOM 操作
4.1 获取与修改 SVG 元素
内联 SVG 的最大优势是可以像普通 HTML 元素一样进行 DOM 操作:
// 选择 SVG 元素
const svg = document.querySelector('.chart');
const circle = svg.querySelector('circle');
// 修改属性
circle.setAttribute('r', '60');
circle.style.fill = 'tomato';
// 添加元素
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', '10');
rect.setAttribute('y', '10');
rect.setAttribute('width', '80');
rect.setAttribute('height', '80');
svg.appendChild(rect);
// 动画
circle.style.transition = 'r 0.3s ease';
circle.setAttribute('r', '60');
4.2 动态创建 SVG
function createSvg(width, height, shapes) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
svg.setAttribute('width', width);
svg.setAttribute('height', height);
shapes.forEach(shape => {
const el = document.createElementNS('http://www.w3.org/2000/svg', shape.type);
Object.entries(shape.attrs).forEach(([k, v]) => {
el.setAttribute(k, v);
});
svg.appendChild(el);
});
return svg;
}
const chart = createSvg(200, 100, [
{ type: 'circle', attrs: { cx: 50, cy: 50, r: 30, fill: 'coral' } },
{ type: 'rect', attrs: { x: 100, y: 20, width: 80, height: 60, fill: 'lightblue' } }
]);
document.body.appendChild(chart);
五、性能考量
5.1 内联 SVG 的体积
内联 SVG 会增加 HTML 文件大小。如果同一个 SVG 在多个页面复用,考虑:
<!-- 页面 1: 使用内联(首次展示)-->
<svg viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</svg>
<!-- 多页面使用: 考虑 SVG Sprite 或外部引用 -->
<!-- 使用 symbol + use -->
<svg style="display: none;">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</symbol>
</svg>
<svg><use href="#icon-home"/></svg>
5.2 渲染性能
对于复杂的 SVG 图形,浏览器渲染性能可能不如位图。使用时注意:
- 避免在 SVG 中使用过于复杂的路径
- 对于静态图形,考虑导出为优化过的 SVG
- 使用
will-change提示浏览器优化
六、实战应用
6.1 图标系统
<!-- SVG Icon 组件 -->
<template id="icon-template">
<svg viewBox="0 0 24 24" width="24" height="24">
<slot></slot>
</svg>
</template>
<script>
class IconComponent extends HTMLElement {
constructor() {
super();
const template = document.getElementById('icon-template');
this.attachShadow({ mode: 'open' }).appendChild(template.content.cloneNode(true));
}
}
customElements.define('icon-component', IconComponent);
</script>
<!-- 使用 -->
<icon-component>
<path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</icon-component>
6.2 数据可视化
<svg viewBox="0 0 400 200" class="chart">
<!-- 柱状图 -->
<g class="bars">
<rect x="10" y="100" width="30" height="100" fill="#3498db" class="bar" data-value="100"/>
<rect x="50" y="60" width="30" height="140" fill="#3498db" class="bar" data-value="140"/>
<rect x="90" y="80" width="30" height="120" fill="#3498db" class="bar" data-value="120"/>
</g>
<!-- X 轴 -->
<line x1="0" y1="200" x2="400" y2="200" stroke="#333" stroke-width="2"/>
</svg>
// 交互式图表
document.querySelectorAll('.bar').forEach(bar => {
bar.addEventListener('mouseenter', () => {
const value = bar.dataset.value;
bar.style.fill = '#e74c3c';
showTooltip(bar, value);
});
bar.addEventListener('mouseleave', () => {
bar.style.fill = '#3498db';
hideTooltip();
});
});
七、面试高频问题
Q: 内联 SVG 和使用 img/object 引用 SVG 有什么区别?
回答要点:内联 SVG 成为 DOM 一部分,可以 CSS 样式化和 JavaScript 操作,但会增加 HTML 体积;img 引用会被浏览器缓存、不可编辑,但体积更小且符合关注点分离;object 引用可以包含脚本和交互,但需要插件支持。
Q: 为什么内联 SVG 的某些属性不能用 CSS 设置?
回答要点:SVG 元素的几何属性(如 cx、r、x、y)本质上是 XML 属性,不是 CSS 属性。SVG2 规范将部分属性(如 fill、stroke)同步为 CSS 属性,但基础几何属性仍需通过 SVG 属性设置。可通过 CSS 变量或 JavaScript 动态设置。