获取富文本(richText)内容的视觉长度

1,831 阅读3分钟

前几天,pm 提了个需求,希望对用户输入的富文本进行长度的限制,如限制最长为 400 ,包含中英文等,即视觉层面上的 400 。该项目使用的富文本编辑器是 tinymce,或许其他编辑器的实现与 tinymce 不同,仅在此阐述下我的做法,提供一个思路。


忽略 Tag

首先,标签肯定是不需要的,包括其中的 style 和可能存在的 attribute。对于标签,很容易地会想到通过标签的 <> 来判别,当遇到 < 不进行计数,遇到 > 再开始计数,因此可以写出下列的代码

const getPlainTextLen = richText => {
    let count = 0
    for (let i = 0; i < richText.length; i++) {
        if (richText[i] === '<') {
            while (richText[i] !== '>') {
                i++
            }
        } else {
            count++
        }
    }
    return count
}

在上面的代码中,我们成功忽略了标签所带来的影响,但是实现的方式未免过于丑陋,我们可以用正则来实现,将 < ... > 替换成空字符串,然后获取长度即可,为了不对原字符串造成影响,函数必须是纯函数

const getPlainTextLen = richText => richText.replace(/<[^>]+>/g, '').length

处理空白字符串和换行

很显然,空白字符串也是我们所不需要的,在 html 中,空白字符串可体现为空格或者是 &nbsp;&ensp; 等,在 JavaScript 中体现为换行的 \n\r\n 也是空格的形式出现,字符串经过 replace 后还是字符串,因此对上面的函数进行扩展

const getPlainTextLen = richText => richText
    .replace(/<[^>]+>/g, '')
    .replace(/ |\n|\r\n|&nbsp;|&ensp;/g, '')
    .length

有些编辑器存储空白字符串仅仅是简单的空格,不会出现其他的形式。为了性能考虑(减少判断),你不必完全照搬第二个 replace , 相反的,诸如 &emsp; 也表示为空白字符串,上面代码仅列举了两种,因此最好根据实际情况进行编写。

处理 HTML Entities (html 字符实体)

如果你输入了 1 + 1< 3,编辑器会怎么存储呢,为什么我输入了 <p> 1234 <p> 也能正确显示出来呢 ?

1 + 1< 3 会被存储为 1 + 1 &lt; 3 ,< 被存储为对于的 html entities,可能是为了规避用户可能输入的 html 字符串。关于 html entities,详细资料可参考 w3schools,链接为 HTML Entities (w3schools.com),如果打不开,请使用一点魔法手段哦,上文提到的 &nbsp; 也属于此,但由于其表现为空白字符串,因此需要提前进行处理。HTML Entities 还包括了很多人喜爱的 😀 等 emoji 表情。像上文提到的 &lt;,原本 1 + 1< 3 的 5 个长度变成了 8 个长度,😀 对应着 &#128512; , 但视觉层面应该视为一个长度,那么,需要对其进行处理。

HTML Entities 有两种表示形式,即 &entity_name; OR &#entity_number; 基于此,编写对应的正则。

// {2,5} 和 {1,6} 表示需要匹配的长度,根据实际情况编写
const getPlainTextLen = richText => richText
    .replace(/<[^>]+>/g, '')
    .replace(/ |\n|\r\n|&nbsp;|&ensp;/g, '')
    .replace(/&([a-z]{2,5}|#[0-9]{1,6});/g, ' ')
    .length

最后,还有特殊的 glyph 字形,即我们小学学过的注音音符,如 a -> à ,其对应为 a&#768; ,诸如此类,应该对应为 1个字符串,让我们编写最后一个正则,其需要在上面代码中的最后一个正则前提前处理,否则 "a" 会变成 "a " 了,会被计算成 2 个字符串,无法准确的判断了。

const getPlainTextLen = richText => richText
    .replace(/<[^>]+>/g, '')
    .replace(/ |\n|\r\n|&nbsp;|&ensp;/g, '')
    .replace(/[a-zA-Z]&#7([6-7][0-9]);/g, ' ')
    .replace(/&([a-z]{2,5}|#[0-9]{1,6});/g, ' ')
    .length

对应字形,并不是必须添加,因为并不是每一个编辑器都能正确地显示,比如我使用的 tinymce 就无法正确地显示,还是那句话,根据实际情况判断是否需要添加。


验证

最后,让我们来验证下该函数是否正确

Snipaste_2021-05-26_03-49-22.png

// 长度为 5 + 8 + 4 + 10 + 11 + 5 + 3 + 4 + 1 = 51
const html =
    `<h1>Title</h1>
     <h2>subtitle</h2>
     <h3>
       H&#769;e&#768;H&#769;e&#768;
     </h3>
     <ul>
       <li> First block </li>
       <li> Second&nbsp;block </li>
       <li> 1 + 1 &lt; 3 </li>
       <li> 5 &#62; 3 </li>
       <li> 1&ensp;00 &#8364; </li>
       <li> &#128512; </li>
     </ul>`

// 视觉层面也为 51 ,结果正确
console.log(getPlainTextLen(html))    // 51

如果这篇文章对你有帮助,请给我点个赞,欢迎下方留言讨论