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
x 和 y 是绝对坐标,指定文本起始点在 SVG 画布上的位置。注意 y 指的是文字基线(baseline)的纵坐标,不是文字顶部,所以新手经常会发现文字比预期位置偏下——这是基线定位导致的。
dx 和 dy 是相对偏移,从"当前文本位置"出发做增量。在 <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是文本左端middle:x是文本中心end:x是文本右端
做居中对齐时,text-anchor="middle" 配合 x="50%" 比手动算坐标简单得多。
dominant-baseline:垂直对齐
dominant-baseline 控制 y 坐标对应文本的哪条基线,常用值:
| 值 | 效果 |
|---|---|
auto | 默认,通常等同于 alphabetic |
alphabetic | y 对齐到西文字母底部基线 |
middle | y 对齐到文字垂直中心 |
hanging | y 对齐到悬挂基线(印度语系常用) |
central | y 对齐到 em 方框中心 |
ideographic | y 对齐到表意文字底部 |
最容易踩的坑:默认 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-wrap、line-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>或 CSSbackground-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 中使用有几个特殊问题:
字体子集化
用 fonttools 或 pyftsubset 只提取用到的字符,把字体文件从几 MB 压缩到几十 KB:
bashpyftsubset NotoSansSC-Regular.ttf --text-file=chars.txt --output-file=NotoSansSC-subset.woff2 --flavor=woff2
导出 SVG 图片时,子集化几乎是必须的,否则要么字体加载失败,要么文件体积爆炸。
SVG 作为图片引用时的中文字体问题
作为 <img> 标签引用时,SVG 无法加载外部字体,中文字符会回退到浏览器默认字体。两种解法:
- Base64 嵌入子集字体:把子集化后的字体 Base64 编码写进 SVG 的
<style>,兼容性最好 - 文字转路径:用设计工具或
text2path工具把文字轮廓转为<path>,彻底消除字体依赖,但文本不再可选、不可编辑、文件体积也会增大
最小字号问题
Chrome 在非 Retina 屏幕下会强制将小于 12px 的中文字体渲染为 12px,这在 SVG 里同样存在。如果确实需要更小的中文字,可以用 transform="scale(0.8)" 配合较大的 font-size 来绕过,但会牺牲清晰度。
各属性的浏览器兼容性速查
| 属性/元素 | Chrome | Firefox | Safari | 备注 |
|---|---|---|---|---|
<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> 手动分行。