中英文文本两端对齐优化
发布于 4 年前 作者 limin 3416 次浏览 来自 分享

两端对齐排版优化

通过如下 css 可以很容易使文本段落两端对齐

.justify{
  text-align: justify;
  word-wrap: break-word;
}

但是,如果段落内包含中英文,并且存在过长的英文单词导致文本换行。这时候,段落中某些行中的文字间距会拉的很大

而我们期望的是如下的效果

总结为排版规则,即为:

段落中的每一行,如果剩余的空白空间大于 n 个字符宽带,则当前行向左对齐,否则两端对齐。

排版规则优化

无论是在游览器环境,还是小程序环境,想要对段落中的每一行独立应用排版规则,需要先通过程序进行分行(根据段落宽度,计算每一行容纳的字符)。

精确的分行,需要使用 canvas 来测量文本的宽度,但是在小程序端,canvas 接口调用的耗时比较长,并且是同步调用,如果大量文本进行测量会长时间阻塞 Javascript 主线程,因此在小程序端的适用范围比较小。

为了避免进行段落分行计算,可以放宽对每一行应用两端对齐排版规则,改为:

如果段落内存在空白空间大于 n 个字符宽度的行,则不使用两端对齐。否则对整个段落应用两端对齐。

这样可以避免出现文字间距拉的很大这种不可接受的情况,并且保证大多数情况下,段落内的每一行能两端对齐。

排版规则实现

排版规则实现的难点是:如何判断段落内是否存在空白空间大于 n 个字符宽度的行。

下面描述了一种算法实现方式:

首先通过对段落文本进行分词,分离连续的 CJK 字符和非 CJK 字符,并在非 CJK 字符串前后添加 flag 标记。
对分词后的内容进行渲染,flag 标记作为空文本元素进行渲染,不占宽度。
渲染后,获取每个 flag 元素的布局位置信息,连续 2 个 flag为一对。分别比较每一对 flag 元素,如果两个 flag 元素不在同一行,则:`` 空白空间宽度 = 段落宽度 - 每对第一个 flag 元素的左边界坐标 ``,如果 `` 空白空间宽度 ``大于 n 个字符宽度,则不对段落进行两端对齐。

分词算法

export function splitCJKWord(texts) {
  const nCJK = /([^\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\s-]+)/g;
  let rtn = [];
  let lastIndex = 0;
  let match;
  do {
    match = nCJK.exec(texts);
    let leftStr;
    let matchStr;
    if (match) {
      leftStr = texts.slice(lastIndex, match.index);
      matchStr = match[0];
    } else {
      leftStr = texts.slice(lastIndex);
    }
    if (leftStr) {
      rtn.push({
        text: leftStr,
        type: 'cjk'
      })
      lastIndex += leftStr.length;
    }
    if (matchStr) {
      const prevWord = rtn[rtn.length - 1];
      let needHideAfterJustify = false;
      if (prevWord && prevWord.text[prevWord.text.length - 1] === ' ') {
        needHideAfterJustify = true;
      }
      rtn.push({
        text: '',
        type: 'flag',
        needHideAfterJustify
      });
      rtn.push({
        text: matchStr,
        type: 'ncjk'
      });
      rtn.push({
        text: '',
        type: 'flag'
      });
      lastIndex += matchStr.length;
    }
  } while (match);
  return rtn;
}

小程序内获取 flag 元素位置信息,并判断是否应用两端对齐规则

const query = wx.createSelectorQuery()
query.selectAll('.flag').boundingClientRect()
query.exec((res) => {
  const items = res[0];
  let leftFlag = null;
  let rightFlag = null;
  let needJustify = true;
  for (let i = 0, len = items.length; i < len; i++) {
      if (i % 2 === 0) {
         leftFlag = items[i];
         continue;
      }
      rightFlag = items[i];
      if (leftFlag.top !== rightFlag.top && paragraphWidth - leftFlag.left > 17 * 3) {
        needJustify = false;
        break;
      }
  }
  this.setData({
      needJustify
  })
})
1 回复

并没有觉得很优雅 哈哈

回到顶部