使用JavaScript将HTML插入文本节点

27

我有一个小文本节点:

var node

而且我希望把每个“lol”用标签包围起来。

node.nodeValue = node.nodeValue.replace(/lol/, "<span>lol</span>")

当我想要一个元素时,它会打印出"<span>lol</span>"而不是"lol"。


在这种情况下,您将不得不用HTML内容替换文本节点。 - Arun P Johny
@ArunPJohny 我该怎么做? - alt
1
@ArunPJohny 文本节点没有 innerHTML 属性。 - alt
@JacksonGariety 你不能这样简单设置,你可能需要编写一些代码来替换文本节点。你能分享文本节点的HTML吗? - Arun P Johny
也许这个SO可以帮助您解决问题,如果您可以使用jQuery的话。https://dev59.com/WlzUa4cB1Zd3GeqPzArq - fmodos
显示剩余3条评论
5个回答

20

安德烈亚斯·乔萨斯提供的答案非常好。然而,当搜索词在同一个文本节点中出现多次时,该代码存在几个错误。这里是修复了这些错误的解决方案,此外插入已经被分解到matchText中以便于更容易使用和理解。现在只有新标签在回调函数中构建并通过返回传递给matchText。

更新后的matchText函数,已修复错误:

var matchText = function(node, regex, callback, excludeElements) { 

    excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
    var child = node.firstChild;

    while (child) {
        switch (child.nodeType) {
        case 1:
            if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1)
                break;
            matchText(child, regex, callback, excludeElements);
            break;
        case 3:
            var bk = 0;
            child.data.replace(regex, function(all) {
                var args = [].slice.call(arguments),
                    offset = args[args.length - 2],
                    newTextNode = child.splitText(offset+bk), tag;
                bk -= child.data.length + all.length;

                newTextNode.data = newTextNode.data.substr(all.length);
                tag = callback.apply(window, [child].concat(args));
                child.parentNode.insertBefore(tag, newTextNode);
                child = newTextNode;
            });
            regex.lastIndex = 0;
            break;
        }

        child = child.nextSibling;
    }

    return node;
};

用法:

matchText(document.getElementsByTagName("article")[0], new RegExp("\\b" + searchTerm + "\\b", "g"), function(node, match, offset) {
    var span = document.createElement("span");
    span.className = "search-term";
    span.textContent = match;
    return span;
});
如果您希望插入锚点(链接)标记而不是 span 标记,请将创建元素更改为 "a" 而不是 "span",添加一行来添加 href 属性到标记,并将 'a' 添加到 excludeElements 列表中,以便在链接内部不会创建链接。

1
我添加了一个修复程序 regex.lastIndex = 0,以在重用正则表达式时重置它。请参见https://dev59.com/gnI_5IYBdhLWcg3wHfOr。 - Jeff
当你有时间的时候,能否请你澄清文本和替换发生的位置,并添加(在此处放置文本)放置(替换文本在此处)? - Fred Mcgiff
searchTerm 是您要搜索的文本。您提供的回调函数接收 DOM 节点、找到的匹配项和所找到内容的偏移量。然后在回调函数中使用这些信息进行任何您想要的操作,例如替换它、给节点着色、拆分文本并在其周围放置标签,或者您现在知道 searchTerm 在 dom 中确切位置后想要执行的任何其他操作。 - d'Artagnan Evergreen Barbosa

17
以下文章提供了将文本替换为HTML元素的代码: http://blog.alexanderdickson.com/javascript-replacing-text 引自该文章:
var matchText = function(node, regex, callback, excludeElements) { 

    excludeElements || (excludeElements = ['script', 'style', 'iframe', 'canvas']);
    var child = node.firstChild;

    do {
        switch (child.nodeType) {
        case 1:
            if (excludeElements.indexOf(child.tagName.toLowerCase()) > -1) {
                continue;
            }
            matchText(child, regex, callback, excludeElements);
            break;
        case 3:
           child.data.replace(regex, function(all) {
                var args = [].slice.call(arguments),
                    offset = args[args.length - 2],
                    newTextNode = child.splitText(offset);

                newTextNode.data = newTextNode.data.substr(all.length);
                callback.apply(window, [child].concat(args));
                child = newTextNode;
            });
            break;
        }
    } while (child = child.nextSibling);

    return node;
}

使用方法:

matchText(document.getElementsByTagName("article")[0], new RegExp("\\b" + searchTerm + "\\b", "g"), function(node, match, offset) {
    var span = document.createElement("span");
    span.className = "search-term";
    span.textContent = match;
    node.parentNode.insertBefore(span, node.nextSibling); 
});

并且解释:

基本上,正确的做法是...

  1. 遍历所有的文本节点。
  2. 在文本节点中找到子字符串。
  3. 在偏移量处将其拆分。
  4. 在拆分中间插入一个 span 元素。

1
这是有关文本节点中多个匹配项的问题:http://jsfiddle.net/vw0eok5t/ - Smern
var child = node.firstChild; 后面应该添加 if (child===null) return; - WW00WW

5

并不是说这是一个更好的答案,但我为了完整性而发布了我所做的事情。在我的情况下,我已经查找或确定了需要在特定的 #text 节点中突出显示的文本的偏移量。这也澄清了步骤。

//node is a #text node, startIndex is the beginning location of the text to highlight, and endIndex is the index of the character just after the text to highlight     

var parentNode = node.parentNode;

// break the node text into 3 parts: part1 - before the selected text, part2- the text to highlight, and part3 - the text after the highlight
var s = node.nodeValue;

// get the text before the highlight
var part1 = s.substring(0, startIndex);

// get the text that will be highlighted
var part2 = s.substring(startIndex, endIndex);

// get the part after the highlight
var part3 = s.substring(endIndex);

// replace the text node with the new nodes
var textNode = document.createTextNode(part1);
parentNode.replaceChild(textNode, node);

// create a span node and add it to the parent immediately after the first text node
var spanNode = document.createElement("span");
spanNode.className = "HighlightedText";
parentNode.insertBefore(spanNode, textNode.nextSibling);

// create a text node for the highlighted text and add it to the span node
textNode = document.createTextNode(part2);
spanNode.appendChild(textNode);

// create a text node for the text after the highlight and add it after the span node
textNode = document.createTextNode(part3);
parentNode.insertBefore(textNode, spanNode.nextSibling);

谢谢你,这对我来说是最好的答案。 - romuleald
如何在数字方面使用它?你能举个例子找到和替换数字吗?例如,将错误的邮政编码替换为正确的邮政编码。尽管这是针对文本的,但我仍在努力弄清楚如何替换数字。 - Fred Mcgiff
当你有时间的时候,能否请你澄清文本和替换发生的位置,并添加(在此处放置文本)放置(替换文本在此处)? - Fred Mcgiff

5

对于现在发现这个问题的人,最新的答案如下:

function textNodeInnerHTML(textNode,innerHTML) {
    var div = document.createElement('div');
    textNode.parentNode.insertBefore(div,textNode);
    div.insertAdjacentHTML('afterend',innerHTML);
    div.remove();
    textNode.remove();
}

想法是在使用以下语句创建新的html元素之后(var div = document.createElement('div');),使用如下技巧将其插入到textNode之前:
textNode.parentNode.insertBefore(div,textNode);

然后使用:
div.insertAdjacentHTML(
 'afterend',
 textNode.data.replace(/lol/g,`<span style="color : red">lol</span>`)
) 

然后使用以下方式删除textNodediv

textNode.remove();
div.remove();
insertAdjacentHTML 不像 innerHTML 那样会销毁事件监听器。
如果您想查找 elm 的所有文本节点后代,则可以使用以下方法:
[...elm.querySelectorAll('*')]
.map(l => [...l.childNodes])
.flat()
.filter(l => l.nodeType === 3);

4
您可能需要将node作为父节点,这样您就可以直接使用innerHTML:
node.innerHTML=node.childNodes[0].nodeValue.replace(/lol/, "<span>lol</span>");

这里的node.childNodes[0]指的是实际的文本节点,而node则是它所在的容器元素。


10
请注意,替换包含节点的innerHTML会破坏可能通过脚本添加了事件监听器的子节点。 - ccjuju
这对我不起作用(Chrome 50);没有视觉变化。我可以在控制台中看到node.innerHTML已更改,但UI没有更改。此外,node.parentNode.innerHTML不包含新更改。 - Jeff
如果parent.Node = body,可能有很多兄弟姐妹,那么你怎么知道要替换哪个子节点呢? - Elliott B
对于未来的读者 - 还有另一个重要的问题 - 这个答案正在将文本内容转换为HTML。如果文本内容恰好包含HTML标记,则它们将不会被转义,最好的情况下会产生意外结果,最坏的情况下可能会构成安全风险。 - lorg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接