如何在页面上加载时更改HTML内容

Nic*_*eek 6 javascript ab-testing optimizely mutation-observers mutation-events

我在我们的网站上进行A/B测试,我做的大部分工作都是在一个JS文件中,在文件顶部加载之前,其他任何东西都被渲染,但是在jQuery加载之后,它有时会派上用场.

举一个非常简单的改变H1标签的例子,我通常会在头部注入一个样式来将H1不透明度设置为0,然后在DOMContentLoaded上,我会操纵H1内容,然后将不透明度设置为1.是为了避免在更改发生之前闪现旧内容 - 隐藏整个对象在眼睛上更优雅.

我已经开始研究MutationObserver API了.我之前在使用覆盖对话框中更改内容时使用过这个用户可以打开的内容,这似乎是一个非常酷的方法,我想知道是否有人设法使用MutationObserver来收听文件,因为它是第一次加载/在首次渲染之前和DOMContentLoaded之前解析并更改文档?

这种方法可以让我更改H1内容而不必隐藏它,更改它,然后显示它.

到目前为止,我已经尝试但失败了,刚刚结束了关于即将到来的突变事件的阅读,并想知道我是否正在尝试做一些不可能的事情.然而,我们(不是我)已经设法将机器人放在火星上,所以我希望我能解决这个问题.

因此,可以使用MutationObservers在加载/解析页面时动态更改HTML内容吗?

感谢您的帮助或任何指示.

问候,尼克

wOx*_*xOm 12

MDN上的文档有一个通用的不完整的例子,并没有展示常见的陷阱. 变异摘要库提供了一个人性化的包装器,但是像所有包装器一样,它增加了开销.请参阅MutationObserver的性能以检测整个DOM中的节点.

创建并启动观察者.

让我们使用递归文档范围的MutationObserver来报告所有添加/删除的节点.

var observer = new MutationObserver(onMutation);
observer.observe(document, {
  childList: true, // report added/removed nodes
  subtree: true,   // observe any descendant elements
});
Run Code Online (Sandbox Code Playgroud)

添加节点的天真枚举.

减慢极大/复杂页面的加载速度,请参阅性能.
有时会错过在父容器中合并的H1元素,请参阅下一节.

function onMutation(mutations) {
  mutations.forEach(mutation, m => {
    [...m.addedNodes]
      .filter(node =>
        node.localName === 'h1' && /foo/.test(node.textContent))
      .forEach(h1 => {
        h1.innerHTML = h1.innerHTML.replace(/foo/, 'bar');
      });
  });
}
Run Code Online (Sandbox Code Playgroud)

有效枚举添加的节点.

现在困难的部分.在加载页面时,变异记录中的节点可能是容器(如整个站点标题块,其所有元素仅报告为一个添加节点):规范不要求每个添加的节点单独列出,因此我们'我必须使用querySelectorAll(极慢)或getElementsByTagName(非常快)查看每个元素.

function onMutation(mutations) {
  for (var i = 0, len = mutations.length; i < len; i++) {
    var added = mutations[i].addedNodes;
    for (var j = 0, node; (node = added[j]); j++) {
      if (node.localName === 'h1') {
        if (/foo/.test(node.textContent)) {
          replaceText(node);
        }
      } else if (node.firstElementChild) {
        for (const h1 of node.getElementsByTagName('h1')) {
          if (/foo/.test(h1.textContent)) {
            replaceText(h1);
          }
        }
      }
    }
  }
}

function replaceText(el) {
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
  for (let node; (node = walker.nextNode());) {
    const text = node.nodeValue;
    const newText = text.replace(/foo/, 'bar');
    if (text !== newText) {
      node.nodeValue = newText;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

为什么丑陋的香草for循环?因为forEachfilter和ES2015 for (val of array)相比非常慢.请参阅MutationObserver的性能以检测整个DOM中的节点.

为什么TreeWalker?保留附加到子元素的任何事件侦听器.要仅更改Text节点:它们没有子节点,更改它们不会触发新的突变,因为我们已经使用过childList: true,而不是characterData: true.

通过实时HTMLCollection处理相对稀有的元素而不计算突变.

因此,我们寻找一个很少使用的元素,如H1标签或IFRAME等.在这种情况下,我们可以使用getElementsByTagName返回的自动更新的HTMLCollection来简化和加速观察者回调.

const h1s = document.getElementsByTagName('h1');

function onMutation(mutations) {
  if (mutations.length === 1) {
    // optimize the most frequent scenario: one element is added/removed
    const added = mutations[0].addedNodes[0];
    if (!added || (added.localName !== 'h1' && !added.firstElementChild)) {
      // so nothing was added or non-H1 with no child elements
      return;
    }
  }
  // H1 is supposed to be used rarely so there'll be just a few elements
  for (var i = 0, h1; (h1 = h1s[i]); i++) {
    if (/foo/.test(h1.textContent)) {
      // reusing replaceText from the above fragment of code 
      replaceText(h1);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)