2,560 次浏览

JavaScript: HTML 内容折叠 (Html Content Ellipsis)

2018 年马上就要过去了,这一年真的经历了太多,本应该按惯例写写年终总结的,不过最近真的太忙了,总有些事情是更优先的,又赶上今天有人需要帮忙,索性就把解决这个小问题的代码讲一讲,全当作是对 2018 年的一个告别吧。

其实很久以前,在某个项目中也遇到过这个问题,我们在一个前端 Table (Grid) 中,希望显示出一个包含了 HTML 样式的富文本内容列,但是其内容很长,我们希望可以按照一个指定的字数进行截取(需要继续保留富文本的样式),并将字符串的剩余部分显示为 …

我们知道,通过 CSS ellipsis 也能设置 … 样式,但是这只能按照外围容器的 width 进行设定,无法按照字数进行处理。所以,为了庆祝新年,我们自己动手写一个吧!

主要目标

  • 根据指定的字符数,对 HTML 字符串进行计数,跳过所有 HTML 标签
  • 统计截止到结束处,所有未闭合的标签,并协助进行追加

基本思路

  • 从目标字符串的第一个字符开始遍历,如果没有遇到 < 就继续遍历并计数,如果遇到 < 则暂停计数,直到遇见下一个 > 时,再继续计数
  • 如果字数计数器达到调用者所指定的 lengthLimit 阈值则停止查询,并记录得到的结束位置
  • 我们可以在上述循环过程中同时记录每一个标签的配对情况,从而帮助我们在最后补齐被截取一半的标签
  • 创建一个数组作为栈结构,遇到一个 HTML 的标签则压栈,遇到与之对应的闭合标签则直接弹栈(如果是自闭合标签则直接跳过),在到达指定字符串位置时,只需将栈内剩余标签弹出并追加到字符串的结尾即可
  • 加上结尾的 … 字符

OK,具体实现如下。

        var testHtmlRichText =
            "Hello <br />, <div><p><b>World</b>! <em>what the hell is <b>that</b></em></p>?! <h1>你好,再见!</h1><div>Tomorrow is another day.</div></div> 明天又是一天";
        // console.log(foldHtmlRichTextToEllipsis(testHtmlRichText, 200, " ..."));
        // console.log(foldHtmlRichTextToEllipsis(testHtmlRichText, 142, " ..."));

        console.log(foldHtmlRichTextToEllipsis(testHtmlRichText, 11, " ..."));
        // OUTPUT: Hello <br />, <div><p><b>Wor</b></p></div> ...

        function foldHtmlRichTextToEllipsis(htmlRichText, lengthLimit, ellipsisSuffix) {

            if (!ellipsisSuffix) {
                ellipsisSuffix = " ...";
            }
            if (lengthLimit >= htmlRichText.length) {
                return htmlRichText + ellipsisSuffix;
            }

            let htmlSymbolStack = [];
            let wordCounter = 0;
            let index = 0;

            while (wordCounter < lengthLimit && index < htmlRichText.length) {
                let currentChar = htmlRichText[index];
                index++;
                if (currentChar === "<") {
                    let htmlTagStart = index;

                    // Jump to the end of the tag
                    while (htmlRichText[index] !== ">" && index < htmlRichText.length) {
                        index++;
                    }

                    let htmlTagEnd = index++;
                    let htmlTagString = htmlRichText.substring(htmlTagStart, htmlTagEnd);
                    // For DEBUG
                    // console.log("Tag:" + htmlTagString);

                    // If it's not a self-closing tag, add it to stack
                    if (!htmlTagString.endsWith("/")) {
                        // Check if it's the closed tag
                        if (htmlTagString.startsWith("/")) {
                            // Check if the top element of the stack is paired.
                            if (htmlSymbolStack[htmlSymbolStack.length - 1] === htmlTagString.replace("/", "")) {
                                // If it is paired, it will directly pop the top element of the htmlSymbolStack
                                htmlSymbolStack.pop();
                            } else {
                                // Else means, there is a HTML tag pairing syntax problem with the source string
                                console.error(
                                    "Please check the source HTML string, there is a HTML tag pairing syntax problem."
                                );
                            }
                        } else {
                            htmlSymbolStack.push(htmlTagString);
                        }
                    }
                } else {
                    wordCounter++;
                }
            }

            let result = htmlRichText.substring(0, index);

            // For DEBUG
            // console.log("HTML Symbol Stack: " + result);
            // htmlSymbolStack.forEach(element => {
            //     console.log(element);
            // });
            let closeElement;
            while (closeElement = htmlSymbolStack.pop()) {
                result += ("</" + closeElement + ">");
            }
            // For DEBUG
            // console.log("Result:" + result);
            return result + ellipsisSuffix;
        }

最后祝大家新年快乐,2019 年安康和顺,财源广进!

About nista

THERE IS NO FATE BUT WHAT WE MAKE.

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注