Fabric.js 文本样式 textAlign = justify 两端对齐不生效,特别是单行文本

2025-12-22 34 浏览 0 评论

这个确实是很多人第一次用 Fabric.js 的 textAlign: 'justify' 时踩的坑。我来详细解释一下:

一、问题原因

Fabric.js 的 textAlign: 'justify' 目前 并不是完整实现的 CSS 两端对齐 。 它的对齐逻辑在源码中( fabric.Textbox_wrapLine_renderTextLines 方法)有这样的限制:

只有当文本是多行 ( \n 分行),并且不是最后一行时,才会对行内空格进行等距分配。

也就是说:

  • ✅ 有换行的多行文本中, 除最后一行外 可以被拉伸对齐;
  • ❌ 单行文本 或 自动换行但无显式换行符的行,不会触发 justify;
  • textAlign: 'justify' 对普通 fabric.Text 对象无效,只对 fabric.Textbox 有部分支持。

二、举个例子

const textbox = new fabric.Textbox(
  '这是一段测试文本\n 用于展示 justify 对齐效果\n 最后一行不会被对齐',
  {
    left: 100,
    top: 100,
    width: 300,
    textAlign: 'justify',
    fontSize: 20,
    fill: '#333',
  }
);

canvas.add(textbox);

✅ 前两行会被两端对齐, ❌ 最后一行不会被对齐。

三、如果你用的是 自动换行 :

比如:

new fabric.Textbox('这是一段比较长的文字希望自动换行并且两端对齐', {
  width: 300,
  textAlign: 'justify',
});

Fabric.js 不会在渲染时真正 检测宽度自动分词对齐 , 因为它没有实现 CSS 的测量逻辑。 所以即使超出宽度换行,仍然不会 justify。

四、解决方法

其实我们很多时候需要的是,单行文本也能实现两端对齐,且不需要换行符,而且默认也是按照空格来分割间隔,对于中文文本来说,基本无效,我们可以修改 _renderTextLine 源码,让它不只按空格分词,而是按字符分布间距 ​。

// ===== Fabric.js 5.2.1 Justify Fix =====
const originalRenderTextLine = fabric.Textbox.prototype._renderTextLine;
fabric.Textbox.prototype._renderTextLine = function (method, ctx, line, left, top, lineIndex) {
  const isLastLine = this._textLines ? lineIndex === this._textLines.length - 1 : false;
  const isJustify = this.textAlign === 'justify';
  // const isJustify = this.textAlign === 'justify' && !isLastLine;
  if (!isJustify) {
    return originalRenderTextLine.call(this, method, ctx, line, left, top, lineIndex);
  }
  const render = (text, x, y) => {
    if (typeof ctx[method] === 'function') ctx[method](text, x, y);
    else ctx.fillText(text, x, y);
  };
  ctx.save();
  ctx.textBaseline = 'alphabetic'; // Fabric 默认基线
  // 检查是否有空格
  const hasSpace = /\s/.test(line);
  const tokens = hasSpace ? line.split(/\s+/).filter(Boolean) : Array.from(line);
  const gapCount = tokens.length - 1;
  if (gapCount <= 0) {
    ctx.restore();
    return originalRenderTextLine.call(this, method, ctx, line, left, top, lineIndex);
  }
  // 计算宽度
  const measure = t => ctx.measureText(t).width;
  const widths = tokens.map(measure);
  const totalWidth = widths.reduce((a, b) => a + b, 0);
  const extra = (this.width - totalWidth) / gapCount;
  // 关键:计算 baseline 偏移量
  // Fabric 内部的行高包含 ascent+descent,而 fillText 以 baseline 绘制
  // 我们通过 fontSize × (1 - lineHeightRatio) / 2 来补偿
  const lineHeight = this.getHeightOfLine(lineIndex);
  const fontSize = this.fontSize * (this._fontSizeMult || 1);
  const baselineOffset = (lineHeight - fontSize) / 2 - fontSize * 0.3; // 经验值调试
  const y = top + baselineOffset;
  let x = left;
  for (let i = 0; i < tokens.length; i++) {
    render(tokens[i], x, y);
    x += widths[i] + extra;
  }
  ctx.restore();
};

将这个代码放置到引入 Fabric.js​ 之后即可,当给图层设置 textAlign = 'justify​' 即可实现两端对齐。


发布评论

发布评论前请先 登录

评论列表 0

暂无评论