内联 SVG

深入理解 HTML 中内联 SVG 的使用:svg 元素、SVG2 属性、DOM 操作、CSS 样式化,以及与 img/object 嵌入方式的对比。

内联 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 动态设置。


参考资料

延展阅读