SVG text 自动换行算法

6,997 阅读2分钟
原文链接: www.jianshu.com

最近做的项目涉及到 web 绘图,采用 d3 操作 svg 实现。图中通常会用到文本,而 svg 里的 text 元素不像普通 DOM 元素那样能很好地处理文本换行,所以要用到一些技巧。

通常有两种做法。

  1. tspantext 拆成多行,重新计算每个tspany 坐标。
<svg xmlns="http://www.w3.org/2000/svg">
  <text font-size="14">
    <tspan x="0" y="10">较长文文本一行</tspan>
    <tspan x="0" y="28">放不下就换行</tspan>
  </text>
</svg>
  1. foreignObject 包裹 DOM 元素,利用 DOM 的文本布局能力自动处理换行。
<svg xmlns="http://www.w3.org/2000/svg">
  <foreignObject width="120" height="50">
      <body xmlns="http://www.w3.org/1999/xhtml">
        <p style="font-size:14px;margin:0;">较长文文本一行放不下就换行</p>
      </body>
    </foreignObject>
</svg>

两种方法各有千秋,方法1可以精确控制换行位置,但是需要计算具体的坐标。方法2无需计算内部坐标,但它是根据单词来分割的,针对长英文单词也无能为力。

笔者碰到的就是单个长英文单词也要换行显示,所以只能用方法1。产品需求是文本根据屏幕大小自动适配,如果宽度不够就换行显示。效果如下图所示:


雷达图

Outperformance 这个单词被截断。原理就是先在 text 元素下插入一个tspan,将单词里的字母逐个填进去,判断元素宽度是否达到限制。如果超过限制就再插入一个 tspan,同时计算新 tspany坐标。由于事先知道 text的具体位置,x保持一致就可以了。代码如下:

function wrapWord(text, width) {
  text.each(function() {
    var text = d3.select(this),
      words = text.text().split('').reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = text.node().getBoundingClientRect().height,
      x = +text.attr('x'),
      y = +text.attr('y'),
      tspan = text.text(null).append('tspan').attr('x', x).attr('y', y);
    while (word = words.pop()) {
      line.push(word);
      const dash = lineNumber > 0 ? '-' : '';
      tspan.text(dash + line.join(''));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(''));
        line = [word];
        tspan = text.append('tspan').attr('x', x).attr('y', ++lineNumber * lineHeight + y).text(word);
      }
    }
  });
}

各位看官如果有更好的办法,欢迎评论,不吝赐教!