当我用 <span> 标记替换字符串时,MutationObserver 创建无限循环

问题描述

我想用 html contenteditable div 替换所有出现的内容。我需要用标签替换出现,以使用 css 对其进行自定义。 但是当我尝试用跨度替换时,MutationObserver 生成无限循环......

document.designMode = "on";

var text = document.getElementById('text');

let observer = new MutationObserver(mutations =>
  mutations.forEach(mutation => {
    console.log(text.textContent);
    // let a = "a";
    // a.style.color = "red";
    text.textContent = text.textContent.replace('/','<span>/</span>');
    observer.disconnect(); 
    const range = document.createrange();
    const textNode = text.firstChild;
    range.setStart(textNode,text.textContent.length);
    range.setEnd(textNode,text.textContent.length);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    // observer.disconnect(); 
  })
);

observer.observe(text,{
  childList: true,characterData: true,subtree: true,});

<p id="text" contenteditable="true">I'm a text will be change</p>

解决方法

无限循环是由于在没有先关闭观察者的情况下用 / 替换键入的 <span>/</span> 字符引起的 - 每次替换都会再次触发观察者。

这个问题的解决方法很简单:更换前关闭观察器,然后再打开:

"use strict";
document.designMode = "on";

var text = document.getElementById('text');

let observer = new MutationObserver(mutations =>
  mutations.forEach(mutation => {
    console.log(text.textContent);
    observer.disconnect(); // turn observer off;
    text.textContent = text.textContent.replace('/','<span>/</span>');

    const range = document.createRange();
    const textNode = text.firstChild;
    range.setStart(textNode,text.textContent.length);
    range.setEnd(textNode,text.textContent.length);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    observe(); // turn back on 
  })
);

const observe = ()=> {

  observer.observe(text,{
    childList: true,characterData: true,subtree: true,});
};
observe();
span { background-color: yellow;}
<p id="text" contenteditable="true">I'm a text will be change</p>

但这只是突出了一个潜在的新问题:代码将 textContent 替换为 HTML,因为它位于文本节点内,以源代码形式显示,并且因为 <span>/</span> 位于文本节点内,用黄色突出显示 SPAN 元素的 CSS 不起作用(它不是 SPAN 元素)。

假设应该将 span 元素呈现为 HTML,那么现在的设计问题就变成了如何用 DOM 中的 SPAN 元素替换新键入的正斜杠,而不是文本节点中的。 Mutation Records 的 mutation.target 属性可能是一个很好的起点,可以将其拆分为三个节点:/ 之前的文本的文本节点,<span>/</span> 的元素节点和 / 的最终文本节点> Games >>id | >>name | >>mode 后面的文本(如果有),然后根据需要将光标重新定位到 span 节点之后。