服务端阅读 05月27日 14:41
SVG 中的文字怎么排版?text、tspan、textPath 各自解决什么问题
网页里的 SVG 图标大家都用过,但一提到 SVG 里放文字,很多人就犯难:换行怎么做?沿曲线排列怎么搞?中文字体加载怎么保证?这几个问题背后,对应的是 SVG 文本体系里三个核心元素——<text>、<tspan>、<textPath>,以及一整套定位和对齐属性。理解它们的分工,SVG 文字排版就不再靠猜。text:SVG 文本的基础容器<text> 是 SVG 里唯一原生的文本渲染元素。它和 HTML 里的文本最大区别是——不会自动换行。你写多少字符,它就渲染成一行,超出的部分直接溢出容器。<svg width="400" height="60"> <text x="20" y="40" font-size="24" fill="#333">这段文字不会自动换行</text></svg>定位属性:x / y 与 dx / dyx 和 y 是绝对坐标,指定文本起始点在 SVG 画布上的位置。注意 y 指的是文字基线(baseline)的纵坐标,不是文字顶部,所以新手经常会发现文字比预期位置偏下——这是基线定位导致的。dx 和 dy 是相对偏移,从"当前文本位置"出发做增量。在 <text> 上单独使用时,效果和 x/y 类似,但在 <tspan> 里配合使用时,才是它真正的价值所在——后面会展开。rotate:逐字符旋转rotate 接受一组角度值,按顺序分配给每个字符:<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 实现多行文本<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> 可以单独设置颜色、字号、字重等,不影响兄弟节点:<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 是基于前一个字符位置的偏移,适合做下标、上标或微调间距:<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 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 决定文本从路径的哪个位置开始排列,支持百分比和绝对长度:<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-baselineSVG 文本的对齐控制比 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-shiftalignment-baseline 控制 <tspan> 相对父元素的基线对齐方式,baseline-shift 做上下偏移——用来做上标下标很方便。不过 baseline-shift 正在被 CSS vertical-align 替代,新项目建议直接用 CSS。字体引用:@font-face 与 foreignObjectSVG 里的字体加载和 HTML 共享同一套机制,但有细微差别。@font-face 在 SVG 中的使用在 HTML 页面里内联的 SVG,直接使用页面的 @font-face 声明即可,字体加载没有额外问题。但如果 SVG 作为 <img> 标签引用,浏览器出于安全限制会阻止加载外部字体——这是最常见的坑。解决方案:把字体文件转成 Base64 嵌入 SVG 内部的 <style> 中:<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 等属性实现自动换行:<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 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:pyftsubset 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> 手动分行。