如何在页面加载时更改 HTML 内容
Posted
技术标签:
【中文标题】如何在页面加载时更改 HTML 内容【英文标题】:How to change the HTML content as it's loading on the page 【发布时间】:2017-01-11 03:01:25 【问题描述】:我在我们的网站上进行 A/B 测试,我的大部分工作都是在一个 JS 文件中进行的,该文件在呈现任何其他内容之前加载在页面顶部,但在 jQuery 加载之后,有时会派上用场。
以更改 H1 标签的一个非常简单的示例为例,我通常会在头部注入一个样式以将 H1 不透明度设置为 0,然后在 DOMContentLoaded 上,我会操纵 H1 内容,然后将不透明度设置为 1。这样做的原因是为了避免在更改发生之前闪现旧内容 - 隐藏整个对象在视觉上更加优雅。
我已经开始研究 MutationObserver API。我之前在更改用户可以打开的覆盖对话框中的内容时使用过它,这似乎是一种很酷的方法,我想知道是否有人设法使用 MutationObserver 在第一次加载时收听文档/在第一次渲染之前和 DOMContentLoaded 之前解析和更改文档?
然后,这种方法可以让我更改 H1 内容,而不必隐藏它、更改它,然后显示它。
到目前为止,我已经尝试过但失败了,并且刚刚阅读了有关即将过时的 Mutation Events 并想知道我是否正在尝试做一些不可能的事情。但是我们(不是我)已经设法在火星上放置了一个机器人,所以我希望我能解决这个问题。
那么是否可以使用 MutationObservers 在页面加载/解析时即时更改 html 内容?
感谢任何帮助或任何指示。
问候, 尼克
【问题讨论】:
您好@wOxxOm - 首先,很抱歉让您在周日担心,但感谢您的回复。其次,你能分享一下你特别担心的事情吗?第三,从 1 到 10,你有多担心?最后,如果您从大量易于搜索的示例之一中获得合适的资源,也许您可以将其作为答案分享,如果它是正确的,我会标记为正确的。感谢您的帮助。 我很想知道这个问题的答案,不久前我正在看这个,但最终因为没有时间或需要完成而暂停了它。 感谢您的反馈@wOxxOm - 确实很有帮助,尽管前两页上的链接都没有提供有效的答案,但是有一些很好的文章。如果您知道 *** 上有重复的线程,如果它被认为是有效的重复,我们可以将此问题链接到该问题作为有效答案。很抱歉浪费了您的时间,非常欢迎您停止回复,也许将您的担忧工作集中在其他地方。祝你好运。 好的,很酷,谢谢...不幸的是,我无法在我的情况下使用库,但是我会看看并感谢您提供指向您之前答案的链接...您的句子很重要“ 2. ...将观察者附加到文档根...”是一个很好的指标,我可能会在这里出错。 我想简化一下描述:puu.sh/r0RGg/5319a0e97e.txt你觉得呢? 【参考方案1】:The docs on MDN 有一个通用的不完整示例,不展示常见的陷阱。 Mutation summary 库提供了一个人性化的包装器,但与所有包装器一样,它增加了开销。 见Performance of MutationObserver to detect nodes in entire DOM。
创建并启动观察者。
让我们使用递归文档范围的 MutationObserver 报告所有添加/删除的节点。
var observer = new MutationObserver(onMutation);
observer.observe(document,
childList: true, // report added/removed nodes
subtree: true, // observe any descendant elements
);
添加节点的简单枚举。
减慢巨大/复杂页面的加载速度,请参阅Performance。 有时会错过合并在父容器中的 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');
);
);
添加节点的高效枚举。
现在是困难的部分。突变记录中的节点可能是加载页面时的容器(例如整个站点标题块,其所有元素仅报告为一个添加的节点):规范doesn't require 每个添加的节点都将单独列出,因此我们将必须使用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;
为什么有两个丑陋的香草for
循环?因为 forEach
和 filter
和 ES2015 for (val of array)
在某些浏览器中可能会非常慢,请参阅 Performance of MutationObserver to detect nodes in entire 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);
【讨论】:
很好,谢谢。我今晚会检查一下。好东西。 嗨,再次感谢您的回复...我正在努力了解 TreeWalker 的使用 - 您说它是为了保留附加到 h1 标记的子元素的任何事件侦听器因此它不会触发新的突变,但是我在没有TreeWalker的情况下通过更改文本节点的值来思考是否错误,由于使用了过滤器,它也不会触发新的突变,也不会干扰任何事件处理程序 - 我们只是在更改 textNode.nodeValue?干杯 在不递归枚举的情况下尝试猜测哪个子节点是正确的文本节点:1)<h1>first <span>second <a>third</a></span></h1>
和 2)<h1><span>first</span> second <a>third</a></h1>
感谢这里的 TreeWalkers 介绍,很好...我知道它可以导航文本节点并检查每个子节点文本值,但是关于事件侦听器的评论而不触发另一个突变 - 我假设这是某个地方的另一个答案?没有迂腐,我只是确保我完全理解。干杯。
不触发另一个突变是一种优化和预防措施,它消除了检查仍然与主要条件匹配的节点是否已被我们的代码更改的需要。【参考方案2】:
我以 A/B 测试为生,我经常使用 MutationObservers 并取得不错的结果,但更多时候我只是进行长轮询,这实际上是大多数 3rd 方平台在使用他们的所见即所得时所做的(有时甚至是他们的代码编辑器)。 50 毫秒的循环不应减慢页面速度或导致 FOUC。
我通常使用一个简单的模式,例如:
var poller = setInterval(function()
if(document.querySelector('#question-header') !== null)
clearInterval(poller);
//Do something
, 50);
您可以像在 jQuery 中使用 document.querySelector 一样使用 sizzle 选择器获取任何 DOM 元素,这有时是您唯一需要库的东西。
事实上,我们在我的工作中经常这样做,以至于我们有一个构建过程和a module library,其中包括一个名为When 的函数,它完全符合您的要求。该特定函数会检查 jQuery 以及元素,但修改库以不依赖 jQuery 将是微不足道的(我们依赖 jQuery,因为它在我们客户的大多数网站上并且我们使用它很多东西)。
说到第 3 方测试平台和 javascript 库,根据实现的不同,许多平台(如 Optimizely、Qubit 和我认为 Monetate)捆绑了一个 jQuery 版本(有时会精简),当执行您的代码,因此如果您使用的是 3rd 方平台,则需要考虑这一点。
【讨论】:
感谢@Beau - 我以前使用过这种模式,但有时它不是防弹的。在这种情况下我会再试一次并报告回来,但是,我想我开始更喜欢使用自然浏览器事件,即使它确实会稍微减少目标受众。 (jQuery - 我们没有将它与我们的 sn-p 合并,我们已经预先加载了它)。干杯。 仅供参考 50ms 是浏览器用来绘制页面的 60fps 上的 3 帧。这是一个巨大的差距(实际上 1 帧差距也很明显)。还有一个更大的问题:在 CPU 密集型复杂页面加载期间,计时器回调可能会随机延迟(我见过 500 毫秒的延迟)。以上是关于如何在页面加载时更改 HTML 内容的主要内容,如果未能解决你的问题,请参考以下文章