2026年5月27日 14:41

SVG 中的文字怎么排版?text、tspan、textPath 各自解决什么问题

网页里的 SVG 图标大家都用过,但一提到 SVG 里放文字,很多人就犯难:换行怎么做?沿曲线排列怎么搞?中文字体加载怎么保证?这几个问题背后,对应的是 SVG 文本体系里三个核心元素——<text><tspan><textPath>,以及一整套定位和对齐属性。理解它们的分工,SVG 文字排版就不再靠猜。

text:SVG 文本的基础容器

<text> 是 SVG 里唯一原生的文本渲染元素。它和 HTML 里的文本最大区别是——不会自动换行。你写多少字符,它就渲染成一行,超出的部分直接溢出容器。

svg
<svg width="400" height="60"> <text x="20" y="40" font-size="24" fill="#333">这段文字不会自动换行</text> </svg>

定位属性:x / y 与 dx / dy

xy 是绝对坐标,指定文本起始点在 SVG 画布上的位置。注意 y 指的是文字基线(baseline)的纵坐标,不是文字顶部,所以新手经常会发现文字比预期位置偏下——这是基线定位导致的。

dxdy 是相对偏移,从"当前文本位置"出发做增量。在 <text> 上单独使用时,效果和 x/y 类似,但在 <tspan> 里配合使用时,才是它真正的价值所在——后面会展开。

rotate:逐字符旋转

rotate 接受一组角度值,按顺序分配给每个字符:

svg
<text x="20" y="40" rotate="0 10 20 30 40 50">ROTATE</text>

每个字母会被旋转对应的度数,适合做装饰性的文字效果。如果值的数量少于字符数,最后一个值会重复应用到剩余字符。

textLength 与 lengthAdjust:强行伸缩文本

textLength 让你指定文本渲染后的目标宽度,lengthAdjust 控制怎么凑到这个宽度——spacing 只调间距,spacingAndGlyphs 连字形一起拉伸。用在需要精确对齐的场景,比如图表刻度标签。

tspan:分行与局部样式的关键

<text> 不能换行,但 <tspan> 可以模拟换行。它是 <text> 的子元素,能独立设置坐标和样式,同时保持和父 <text> 的文本流关系。

用 tspan 实现多行文本

svg
<text x="20" y="30" font-size="18" fill="#333"> <tspan x="20" dy="0">第一行文字</tspan> <tspan x="20" dy="1.4em">第二行文字</tspan> <tspan x="20" dy="1.4em">第三行文字</tspan> </text>

这里每个 <tspan> 都重新指定了 x="20",确保每行从同一个左边距开始;dy="1.4em" 控制行距。如果不重新设置 x,后续 <tspan> 会紧跟前一个的文本末尾继续排列,而不是换行——这是很多人踩的坑。

局部样式覆盖

<tspan> 可以单独设置颜色、字号、字重等,不影响兄弟节点:

svg
<text x="20" y="40" font-size="16" fill="#333"> 普通 text 里 <tspan fill="red" font-weight="bold">红色加粗的部分</tspan> 恢复普通样式 </text>

dx / dy 在 tspan 中的妙用

<tspan> 上用 dx/dy 是基于前一个字符位置的偏移,适合做下标、上标或微调间距:

svg
<text x="20" y="40" font-size="20"> H<tspan dy="5" font-size="14">2</tspan><tspan dy="-5">O</tspan> </svg>

注意 dy 是累积的,第二个 <tspan> 需要 dy="-5" 把基线拉回来,否则后面的文字会一直偏移下去。

textPath:沿路径排列文字

<textPath> 让文字沿着任意 SVG 路径排列,这是 SVG 文本最独特的能力——HTML CSS 做不到这件事。

svg
<svg width="300" height="150"> <defs> <path id="curve" d="M 30,100 C 80,20 220,20 270,100" fill="none" /> </defs> <text font-size="16" fill="#333"> <textPath href="#curve">文字沿曲线排列的效果</textPath> </text> </svg>

startOffset:控制起始位置

startOffset 决定文本从路径的哪个位置开始排列,支持百分比和绝对长度:

svg
<textPath href="#curve" startOffset="50%">从路径中间开始</textPath>

配合 text-anchor 可以实现居中对齐——startOffset="50%" + text-anchor="middle" 是最常用的居中方案。

method 与 spacing 属性

  • method="align"(默认):每个字形独立对齐到路径,字符间距不均匀。
  • method="stretch":字形会被拉伸以贴合路径曲率,间距更均匀但字形可能变形。
  • spacing="auto":浏览器自动调整间距;spacing="exact":严格按字符宽度计算,曲率大的地方可能出现重叠。

路径方向与文字朝向

路径的绘制方向决定了文字的朝向。如果路径从右向左画,文字就会倒过来。遇到这种情况,要么调整路径方向,要么对文字加 transform="scale(1,-1)" 翻转。

文本对齐:text-anchor 与 dominant-baseline

SVG 文本的对齐控制比 HTML 更细粒度,但也更容易让人困惑。

text-anchor:水平对齐

text-anchor 决定 x 坐标对应文本的哪个位置:

  • start(默认):x 是文本左端
  • middlex 是文本中心
  • endx 是文本右端

做居中对齐时,text-anchor="middle" 配合 x="50%" 比手动算坐标简单得多。

dominant-baseline:垂直对齐

dominant-baseline 控制 y 坐标对应文本的哪条基线,常用值:

效果
auto默认,通常等同于 alphabetic
alphabeticy 对齐到西文字母底部基线
middley 对齐到文字垂直中心
hangingy 对齐到悬挂基线(印度语系常用)
centraly 对齐到 em 方框中心
ideographicy 对齐到表意文字底部

最容易踩的坑:默认 alphabetic 基线下,中文文字看起来会比预期偏下。在圆形中心放文字时,用 dominant-baseline="central" + text-anchor="middle" 是最靠谱的组合。

alignment-baseline 与 baseline-shift

alignment-baseline 控制 <tspan> 相对父元素的基线对齐方式,baseline-shift 做上下偏移——用来做上标下标很方便。不过 baseline-shift 正在被 CSS vertical-align 替代,新项目建议直接用 CSS。

字体引用:@font-face 与 foreignObject

SVG 里的字体加载和 HTML 共享同一套机制,但有细微差别。

@font-face 在 SVG 中的使用

在 HTML 页面里内联的 SVG,直接使用页面的 @font-face 声明即可,字体加载没有额外问题。但如果 SVG 作为 <img> 标签引用,浏览器出于安全限制会阻止加载外部字体——这是最常见的坑。

解决方案:把字体文件转成 Base64 嵌入 SVG 内部的 <style> 中:

svg
<svg xmlns="http://www.w3.org/2000/svg"> <style> @font-face { font-family: 'CustomFont'; src: url('data:font/woff2;base64,...') format('woff2'); } text { font-family: 'CustomFont', sans-serif; } </style> <text x="20" y="40" font-size="24">自定义字体文本</text> </svg>

foreignObject 引入 HTML 文本

<foreignObject> 允许在 SVG 中嵌入 HTML 片段,从而直接使用 CSS 的 word-wrapline-height 等属性实现自动换行:

svg
<foreignObject x="20" y="20" width="300" height="200"> <div xmlns="http://www.w3.org/1999/xhtml" style="font-size:16px; line-height:1.6;"> 这段文字可以自动换行,支持完整的 CSS 排版能力。 </div> </foreignObject>

<foreignObject> 有明显限制:

  • 作为 <img> 引用的 SVG 不支持 <foreignObject>
  • 跨浏览器渲染差异大,Safari 尤其容易出问题
  • 导出为 PNG/SVG 图片时,<foreignObject> 内容经常丢失

所以它更适合内联在 HTML 页面中的 SVG,不适合导出场景。

多行文本的策略选择

SVG 文本不自动换行,多行文本需要根据场景选方案:

方案适用场景优点缺点
多个 <tspan> + dy固定行数的标签、标题纯 SVG,兼容性好需手动分行,不能自动换行
多个 <text> 元素需要独立定位的多段文字每行独立控制不在同一文本流中
<foreignObject> + HTML长文本、需要自动换行CSS 排版能力强不支持导出,兼容性差
JavaScript 动态拆行不确定文本长度、需要导出自动化,兼容性好实现复杂

对于图表标签、图例这类固定短文本,<tspan> 方案最稳妥。对于用户输入的长文本,要么用 <foreignObject>(仅限内联 SVG),要么用 JS 按字符宽度计算拆行点。

文本选择与复制

SVG 文本默认可以被鼠标选中并复制,前提是 SVG 内联在 HTML 中。但实际体验比 HTML 文本差很多:

  • 选区高亮经常和文字位置对不上,尤其在有 transform 的情况下
  • <textPath> 里的文字选择体验最差,选区是沿路径弯曲的,但复制出来的文本是正常的
  • <tspan> 的选择在某些浏览器中会中断
  • 作为 <img> 或 CSS background-image 引用的 SVG,文本完全不可选

如果需要保证文本可复制,SVG 必须内联到 HTML 中,且避免复杂的 transform 变换。

可访问性

SVG 文本的可访问性比 HTML 差一截,但可以补救:

svg
<svg role="img" aria-labelledby="chart-title chart-desc"> <title id="chart-title">月度销售趋势</title> <desc id="chart-desc">1月至6月的销售数据折线图,整体呈上升趋势</desc> <!-- 图表内容 --> <text x="20" y="40">1月</text> </svg>

关键做法:

  • <svg>role="img"aria-labelledby,引用内部的 <title><desc>
  • 装饰性文字加 aria-hidden="true" 防止屏幕阅读器重复朗读
  • 屏幕阅读器对 SVG 内 <text> 的支持不一致,JAWS 只读 aria-labelledby 指向的内容,NVDA 的行为不稳定
  • 如果文字信息很重要,在 SVG 外部用 HTML 提供一份完整的文字描述是最安全的做法

中文字体处理

中文字体文件动辄数 MB,在 SVG 中使用有几个特殊问题:

字体子集化

fonttoolspyftsubset 只提取用到的字符,把字体文件从几 MB 压缩到几十 KB:

bash
pyftsubset NotoSansSC-Regular.ttf --text-file=chars.txt --output-file=NotoSansSC-subset.woff2 --flavor=woff2

导出 SVG 图片时,子集化几乎是必须的,否则要么字体加载失败,要么文件体积爆炸。

SVG 作为图片引用时的中文字体问题

作为 <img> 标签引用时,SVG 无法加载外部字体,中文字符会回退到浏览器默认字体。两种解法:

  1. Base64 嵌入子集字体:把子集化后的字体 Base64 编码写进 SVG 的 <style>,兼容性最好
  2. 文字转路径:用设计工具或 text2path 工具把文字轮廓转为 <path>,彻底消除字体依赖,但文本不再可选、不可编辑、文件体积也会增大

最小字号问题

Chrome 在非 Retina 屏幕下会强制将小于 12px 的中文字体渲染为 12px,这在 SVG 里同样存在。如果确实需要更小的中文字,可以用 transform="scale(0.8)" 配合较大的 font-size 来绕过,但会牺牲清晰度。

各属性的浏览器兼容性速查

属性/元素ChromeFirefoxSafari备注
<text> 基础属性全支持全支持全支持
<tspan>全支持全支持全支持
<textPath>全支持全支持全支持href 替代 xlink:href
textLength / lengthAdjust支持支持部分Safari 对 lengthAdjust 支持不完整
dominant-baseline支持支持部分缺失Safari 某些值不生效
<foreignObject>支持支持有限制导出场景不可靠

SVG 文本排版看起来属性多、坑不少,但理清 text(定位容器)、tspan(分行与局部样式)、textPath(路径排列)三者的分工,再掌握 text-anchor/dominant-baseline 对齐、字体加载策略和中文字体处理,大部分排版需求都能应对。遇到自动换行需求时,先确认 SVG 是内联还是导出场景——这决定了你能用 <foreignObject> 还是必须回到 <tspan> 手动分行。

标签:SVG