怎样实现完美两端对齐/齐头尾功能——hyphenjs

因为之前代码写得太乱了。最近在重写,忘了开分支。有兴趣查看之前代码的朋友可以重置回c8034eb这个commit之前的代码看。重写完成后会重写一篇文章,抱歉啦。

前言

话不多说,先上图。后者红框里是浏览器默认的文本排版,右侧会有锯齿(至于难不难看就见仁见智啦哈哈)。前者是使用自己开发的hyphenjs后的文本排版,整齐得像一块豆腐块!对于一个处女座来说,简直舒心了很多。去看看这个神奇的hyphenjs

《怎样实现完美两端对齐/齐头尾功能——hyphenjs》

《怎样实现完美两端对齐/齐头尾功能——hyphenjs》

应用场景

不会无缘无故的造轮子。在日常工作需求中,设计师丢给你一个PSD,发现里面的文本是两端对齐的,然后发现实际开发中,两端对齐text-align:justify;这种操作根本是不行的,它会拉开单词空格的距离,十分难看。PSD里之所以好看,是因为设计师里进行了字号或者断行的微调。当然大家都知道,在实际开发中根本行不通的。作为一个还蛮有追求的前端开发,在社区里找不到满意的工具库后,果断开始自己动手,创造幸福。

技术分析

简单来说,开发hyphenjs中(hyphenjs只考虑西文,也就是英文、印尼文等),遇到的三个难点:

  1. 断行的时机,哪个字符适合断行?
  2. 在每行的末位适不适合加连接符-呢?
  3. 如何让其每行长度一致,排版精致风骚呢?

第一点,本来就是为了实现两端对齐,所以,每行的长度要接近文本盒子的宽度才能达到目的。问题来了,盒子的宽度轻轻松松的可以获取:(getComputedStyle(target))['width'],那么行长度如何控制呢?请看以下代码:

    var span = document.createElement('span'), width = 0;
    span.style.fontSize = 18;
    span.innerText = 'R';
    target.appendChild(span);
    width= (span.getBoundingClientRect())['width'];

通过这个方法的思想,也就获取了每个字符的真实宽度,需要注意的几个方面,fontSize需要从文本盒子获知,这里只是假设一个字号而已。同时,span只有插入到文本盒子(或者说DOM树)中,才能真正生效,去获取它的所有真实的属性(同时也会继承父级的相关样式,比如font-family等我们关心的属性)。
因此,最后我们可以获取文本盒子里的所有出现过的字符的宽度,把它们的charCode和宽度用hashMap记录下来,方便复用。

怎样计算每行的累积宽度呢?请看下面的函数:

// 计算n到m字符间的累计宽度
function getAccWidth(textData, charArray, from, to) {
    return charArray.slice(from, to).reduce(function(acc, cur) {
        if (!cur) return acc + 0;
        return acc + textData[PREFIX + cur.charCodeAt()].width;
    }, 0);
}

上面计算到的hashMap终于派上用场了,逐个访问文本里的每个字符的charCode,通过hashMap(也就是函数的形参textData)得到了该字符的宽度,累积算出从n到m的总宽度,和盒子宽度作为比较,若盒子宽度介于当前字符与当前的下一个字符各自的累积宽度,即应当为适合断行的时机。举个栗子,比如第一行,那么from为0, to假如到第21个字符,诶哟刚好大于盒子宽度咯,那么20~21就是刚好适合断行的时机。

那么第一个问题得到解决!紧接着来~虽然粗糙的断行了,怎样精致风骚断行?断行适不适合加连接符?我特意去谷歌搜了几张英语报纸观摩了一下,发现几个特点:

  1. 单词如果被腰斩了,那么要加!但是最多从第二个字符开始~
  2. 特殊字符如\"\':;,.?()[]{}<>~!@#$%^&*-+=/\\|1234567890不需要加,为虾米?看着不舒服啊~
  3. 逗号句号等标点符号不会呆在每行的开头。

那么在断行的时机里加上以上的判断,就可以实现风骚断行了!

然鹅问题来了,当我风骚地把成品发给公司的前辈看的时候,前辈轻轻的飘来一句,还是没有对齐啊···内心受到了暴击,的确,因为之前的三个条件导致断行发生了一些小小的飘移,所以的确也不能完好对齐了。那么怎么解决呢?控制字间距啊!祭出letter-spacing这个神器!(听说报纸排版也是微调字间距实现豆腐块式的排版的)。
公式也很轻而易举就想出来了:合理字间距 = (盒子宽度 – 行宽度)/ 该行字符数
将字间距应用到每一行的行内样式即可。Duang!完成啦哈哈哈~~

最后稍微提几点有趣的地方和注意的地方:

  1. 空格的宽度怎么获取?直接添加到DOM里是被忽略的,哭···这个纠结了一段时间,后来发现x x减掉xx就可以了
  2. 多次对同一个文本应用hyphenjs怎么办?这个一开始没考虑到,后来将文本保存到全局对象中window.hyphen_cached,需要使用的时候随时提取~
  3. hyphenjs目前只应用于纯文本~比如说<p>hyphenjs is <strong>absolutely</strong> helpful.</p>里面的strong标签就会忽略,因为hyphenjs获取的是innerText~

这是我第一次写博客,然后自己也是刚毕业两个月的小白,写得不好的地方还请大家多多见谅~当然最重要的是,如果这个hyphenjs对你的工作产生了帮助,还请不吝给个star哦,或者发现了什么问题,也可以提个issue哦~

有个前辈和我说,“凡事都要遵循朴素的实用主义嘛”,感触很深,开发出的任何东西基本上都需要实用,这样才能应用到实际开发中,才能对自己的工作和成长带来帮助。画风一转,嘿嘿嘿···狠狠戳这里hyphenjs

    原文作者:bestar
    原文地址: https://segmentfault.com/a/1190000010870919
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞