简体   繁体   English

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

[英]How to change the HTML content as it's loading on the page

I do A/B Testing on our site and I do most of my work is in a JS file that is loaded at the top of the page before anything else is rendered but after jQuery has loaded which comes in handy at times.我在我们的网站上进行 A/B 测试,我的大部分工作都是在一个 JS 文件中进行的,该文件在呈现任何其他内容之前加载到页面顶部,但在 jQuery 加载之后,有时会派上用场。

Taking a very simple example of changing an H1 tag, I would normally inject a style in the head to set the H1 opacity to 0 and then on DOMContentLoaded, I would manipulate the H1 contents and then set the opacity to 1. The reason for this is to avoid a flash of the old content before the change takes place - hiding the whole object is more graceful on the eye.举一个更改 H1 标签的非常简单的例子,我通常会在头部注入一个样式,将 H1 不透明度设置为 0,然后在 DOMContentLoaded 上,我将操作 H1 内容,然后将不透明度设置为 1。这样做的原因是为了避免在更改发生之前旧内容的闪现 - 隐藏整个对象在眼睛上更优雅。

I've started to look at the MutationObserver API.我已经开始研究 MutationObserver API。 I've used this before when changing content in an overlay dialog box that the user could open which seems to be quite a cool approach and I'm wondering if anyone has managed to use a MutationObserver to listen to the document as it's first loading/ parsing and make changes to the document before first render and before DOMContentLoaded?我之前在更改用户可以打开的覆盖对话框中的内容时使用过这个,这似乎是一种很酷的方法,我想知道是否有人设法使用 MutationObserver 来收听文档,因为它是第一次加载/在首次渲染之前和 DOMContentLoaded 之前解析和更改文档?

This approach would then let me change the H1 content without having to hide it, change it, then show it.这种方法可以让我更改 H1 内容,而不必隐藏、更改它,然后显示它。

I've attempted but failed so far and have just ended up reading about the to-be-obselete Mutation Events and wondering if I'm trying to do something that just isn't possible.我已经尝试过但到目前为止都失败了,并且刚刚阅读了有关即将过时的突变事件并想知道我是否正在尝试做一些不可能的事情。 However we've (not me) have managed to put a robot on Mars so I'm hoping I can solve this.然而,我们(不是我)已经设法在火星上放置了一个机器人,所以我希望我能解决这个问题。

So is it possible to use MutationObservers to change the HTML content on-the-fly as the page is being loaded/ parsed?那么是否可以在加载/解析页面时使用 MutationObservers 即时更改 HTML 内容?

Thanks for any help or any pointers.感谢您的任何帮助或任何指示。

Regards, Nick问候, 尼克

The docs on MDN have a generic incomplete example and don't showcase the common pitfalls. MDN 上的文档有一个通用的不完整示例,没有展示常见的陷阱。 Mutation summary library provides a human-friendly wrapper, but like all wrappers it adds overhead. 变异摘要库提供了一个人性化的包装器,但与所有包装器一样,它增加了开销。 See Performance of MutationObserver to detect nodes in entire DOM .请参阅MutationObserver 的性能以检测整个 DOM 中的节点

Create and start the observer.创建并启动观察者。

Let's use a recursive document-wide MutationObserver that reports all added/removed nodes.让我们使用递归文档范围的 MutationObserver 来报告所有添加/删除的节点。

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

Naive enumeration of added nodes.添加节点的简单枚举。

Slows down loading of enormously big/complex pages, see Performance .减慢极大/复杂页面的加载速度,请参阅性能
Sometimes misses the H1 elements coalesced in parent container, see the next section.有时会错过在父容器中合并的 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');
      });
  });
}

Efficient enumeration of added nodes.添加节点的有效枚举。

Now the hard part.现在是困难的部分。 Nodes in a mutation record may be containers while a page is being loaded (like the entire site header block with all its elements reported as just one added node): the specification doesn't require each added node to be listed individually, so we'll have to look inside each element using querySelectorAll (extremely slow) or getElementsByTagName (extremely fast).当页面正在加载时,突变记录中的节点可能是容器(就像整个站点标题块,其所有元素都报告为一个添加的节点):规范不要求单独列出每个添加的节点,因此我们必须使用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;
    }
  }
}

Why the two ugly vanilla for loops?为什么有两个丑陋的香草for循环? Because forEach and filter and ES2015 for (val of array) could be very slow in some browsers, see Performance of MutationObserver to detect nodes in entire DOM .因为forEachfilter以及 ES2015 for (val of array)在某些浏览器中可能会非常慢,请参阅MutationObserver 的性能以检测整个 DOM 中的节点

Why the TreeWalker ?为什么是树行者 To preserve any event listeners attached to sub-elements.保留附加到子元素的任何事件侦听器。 To change only the Text nodes: they don't have child nodes, and changing them doesn't trigger a new mutation because we've used childList: true , not characterData: true .仅更改Text节点:它们没有子节点,更改它们不会触发新的变化,因为我们使用了childList: true ,而不是characterData: true

Processing relatively rare elements via live HTMLCollection without enumerating mutations.通过实时 HTMLCollection 处理相对稀有的元素,而无需枚举突变。

So we look for an element that is supposed to be used rarely like H1 tag, or IFRAME, etc. In this case we can simplify and speed up the observer callback with an automatically updated HTMLCollection returned by getElementsByTagName.因此,我们寻找一个应该很少使用的元素,如 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);
    }
  }
}

I do A/B testing for a living and I use MutationObservers fairly often with good results, but far more often I just do long polling which is actually what most of the 3rd party platforms do under the hood when you use their WYSIWYG (or sometimes even their code editors).我以 A/B 测试为生,我经常使用 MutationObservers,结果很好,但更多时候我只是做长轮询,这实际上是大多数 3rd 方平台在使用他们的 WYSIWYG(或有时甚至他们的代码编辑器)。 A 50 millisecond loop shouldn't slow down the page or cause FOUC. 50 毫秒的循环不应减慢页面速度或导致 FOUC。

I generally use a simple pattern like:我通常使用一个简单的模式,如:

var poller = setInterval(function(){
  if(document.querySelector('#question-header') !== null) {
    clearInterval(poller);

    //Do something
  }
}, 50);

You can get any DOM element using a sizzle selector like you might in jQuery with document.querySelector, which is sometimes the only thing you need a library for anyway.您可以使用 sizzle 选择器获取任何 DOM 元素,就像在 jQuery 中使用 document.querySelector 一样,这有时是您唯一需要库的东西。

In fact we do this so often at my job that we have a build process and a module library which includes a function called When which does exactly what you're looking for.事实上,我们在我的工作中经常这样做,我们有一个构建过程和一个模块库,其中包括一个名为When的函数,它完全符合您的要求。 That particular function checks for jQuery as well as the element, but it would be trivial to modify the library not to rely on jQuery (we rely on jQuery since it's on most of our client's sites and we use it for lots of stuff).这个特定的函数会检查 jQuery 和元素,但是修改库而不依赖于 jQuery 是微不足道的(我们依赖 jQuery,因为它在我们客户的大多数站点上,我们将它用于很多东西)。

Speaking of 3rd party testing platforms and javascript libraries, depending on the implementation a lot of the platforms out there (like Optimizely, Qubit, and I think Monetate) bundle a version of jQuery (sometime trimmed down) which is available immediately when executing your code, so that's something to look into if you're using a 3rd party platform.说到第 3 方测试平台和 javascript 库,根据实施情况,许多平台(如 Optimizely、Qubit 和我认为 Monetate)捆绑了一个 jQuery 版本(有时被精简),在执行代码时立即可用,因此如果您使用的是第 3 方平台,则需要考虑这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM