是否有“元素渲染”事件?
Posted
技术标签:
【中文标题】是否有“元素渲染”事件?【英文标题】:Is there 'element rendered' event? 【发布时间】:2013-03-30 07:12:41 【问题描述】:我需要在我的网络应用程序中准确测量文本的尺寸,我通过创建一个元素(使用相关的 CSS 类)、设置其 innerhtml
然后使用 appendChild
将其添加到容器中来实现。
完成此操作后,在渲染元素之前有一个等待,并且可以读取其offsetWidth
以了解文本的宽度。
目前,我正在使用setTimeout(processText, 100)
等待渲染完成。
是否有任何我可以收听的回调,或者更可靠的方式来判断我创建的元素何时被渲染?
【问题讨论】:
如果有人发现这个问题,可以在这里***.com/a/5629730/697388和***.com/a/850995/697388找到好的答案 How to check if element exists in the visible DOM?的可能重复 Is is possible to determine when an element has been rendered using javascript?的可能重复 【参考方案1】:接受的答案来自 2014 年,现已过时。 setTimeout
可能有效,但不是最干净的,也不一定保证元素已添加到 DOM。
从 2018 年开始,您应该使用 MutationObserver 来检测元素何时被添加到 DOM。 现在所有现代浏览器(Chrome 26+、Firefox)都广泛支持 MutationObservers 14+、IE11、Edge、Opera 15+等)。
将元素添加到 DOM 后,您将能够检索其实际尺寸。
这是一个简单示例,说明如何使用 MutationObserver
来监听元素何时添加到 DOM。
为简洁起见,我使用 jQuery 语法来构建节点并将其插入到 DOM 中。
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations)
if (document.contains(myElement))
console.log("It's in the DOM!");
observer.disconnect();
);
observer.observe(document, attributes: false, childList: true, characterData: false, subtree:true);
$("body").append(myElement); // console.log: It's in the DOM!
observer
事件处理程序将在document
中添加或删除任何节点时触发。在处理程序内部,我们然后执行contains
检查以确定myElement
现在是否在document
中。
您不需要遍历存储在 mutations
中的每个 MutationRecord,因为您可以直接对 myElement
执行 document.contains
检查。
要提高性能,请将 document
替换为 DOM 中将包含 myElement
的特定元素。
【讨论】:
@ErikGrosskurth document.contains 也适用于TextNode
。这是一个示例:jsfiddle.net/ef9yub3y 但根据 MDN,它可能不适用于较旧的 IE。如果您在实施时遇到问题,请随时在 SO 上发帖,我很乐意看看。
带有文本的东西是 dom 元素被渲染,然后文本进一步变异,观察者已经通过 if 语句输入,因此观察者持有初始快照。它可能只是一个镀铬的东西。不确定
我正在使用一个包,但不知道它何时会将我的 html 加载到它的内部。我在“.contains”之前扔了一个“if (document.getElementById...”,效果很好!
OP 想要元素被“渲染”的时间。当突变记录触发时,您可以确定该元素尚未被渲染。它在 DOM 中,是的,但是在同步调用 append() 之后,你只是一个微任务,渲染器还没有机会启动。 setTimeout()
确实不是一个好的解决方案,但它比突变记录更接近渲染。如果他们只是想知道元素何时在 DOM 中,这很简单:在调用 append() 之后。
如果您正在寻找 Angular 版本 - nitayneeman.com/posts/…【参考方案2】:
目前没有 DOM 事件指示元素已完全呈现(例如,附加的 CSS 应用和绘制)。这会使一些 DOM 操作代码返回错误或随机的结果(例如获取元素的高度)。
使用 setTimeout 给浏览器一些渲染开销是最简单的方法。使用
setTimeout(function(), 0)
也许是最实际准确的,因为它将您的代码放在活动浏览器事件队列的末尾,没有任何延迟 - 换句话说,您的代码在渲染操作(以及当时发生的所有其他操作)之后立即排队)。
【讨论】:
值得指出的是,有时您需要过一段时间。我在尝试获取容器的滚动高度时遇到问题,该属性有时为 0,有时是正确的。为获取此属性添加 500 毫秒延迟修复了该问题。 听起来你所做的不仅仅是一次重绘,@markthewizard1234。 我认为这个答案在没有完整真相的情况下被接受。 OP 要求“渲染事件”,这可能(理论上,如果它有效)只会让您知道元素在 DOM 中,但并不是所有渲染都已完成。我想知道ResizeObserver 是否会做需要做的事情? @oliver ResizeObserver 在观察者初始化时报告它的第一个事件,这可以在任何时间点。你能详细说明缺乏真相吗? 好建议!它在下一个序列中工作: 1. 我正在更改 dom 元素,例如 - 删除类。 2. setTimeout(doNext, 0)。在doNext
函数中,我的期望是实现并渲染了一个 dom 元素更改 - 实现。【参考方案3】:
This blog post By Swizec Teller,建议使用requestAnimationFrame
,并检查元素的size
。
function try_do_some_stuff()
if (!$("#element").size())
window.requestAnimationFrame(try_do_some_stuff);
else
$("#element").do_some_stuff();
;
实际上它只会重试一次。因为无论如何,到下一个渲染帧,无论是 60 秒还是一分钟,元素都会被渲染。
您实际上需要稍等片刻才能获得渲染后的时间。 requestAnimationFrame 在下一次绘制之前触发。所以requestAnimationFrame(()=>setTimeout(onrender, 0))
是在元素被渲染之后。
【讨论】:
另外,'size' 对我不起作用,相反,我必须检查元素的高度(这是一个可编辑的 div) if (!$("#element").height ())【参考方案4】:MutationObserver 可能是最好的方法,但这里有一个可能有效的简单替代方法
我有一些 javascript 为大表构建 HTML,并将 div 的 innerHTML 设置为生成的 HTML。如果我在设置 innerHTML 后立即获取Date()
,我发现时间戳是在表格完全呈现之前的一段时间。我想知道渲染需要多长时间(这意味着我需要在渲染完成后检查Date()
)。我发现我可以通过设置 div 的 innerHTML 然后(在同一个脚本中)调用页面上某个按钮的 click 方法来做到这一点。只有在 HTML 完全呈现后,才会执行单击处理程序,而不仅仅是在设置 div 的 innerHTML 属性之后。我通过将点击处理程序生成的 Date() 值与设置 div 的 innerHTML 属性的脚本检索到的 Date() 值进行比较来验证这一点。
希望有人觉得这很有用
【讨论】:
【参考方案5】:在我的情况下,setTimeout
或 MutationObserver
之类的解决方案并不完全可行。
相反,我使用了 ResizeObserver。根据MDN:
如果遵循规范,实现应该调用 在绘制之前和布局之后调整事件的大小。
所以基本上观察者总是在布局之后触发,因此我们应该能够获得被观察元素的正确尺寸。
作为奖励,观察者已经返回了元素的尺寸。因此我们甚至不需要调用 offsetWidth
之类的东西(即使它也应该可以工作)
const myElement = document.createElement("div");
myElement.textContent = "test string";
const resizeObserver = new ResizeObserver(entries =>
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
// Note: older versions of Firefox (<= 91) provided a single size object instead of an array of sizes
// https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
);
resizeObserver.observe(myElement);
document.body.append(myElement);
我们也可以像这样包裹在一个方便的异步函数中:
function appendAwaitLayout(parent, element)
return new Promise((resolve, reject) =>
const resizeObserver = new ResizeObserver((entries) =>
resizeObserver.disconnect();
resolve(entries);
);
resizeObserver.observe(element);
parent.append(element);
);
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) =>
console.log(entries)
// do stuff here ...
);
【讨论】:
【参考方案6】:假设你的元素有类名 class="test" 如果发生更改,以下功能继续测试 如果是,运行函数
function addResizeListener(elem, fun)
let id;
let style = getComputedStyle(elem);
let wid = style.width;
let hei = style.height;
id = requestAnimationFrame(test)
function test()
let newStyle = getComputedStyle(elem);
if (wid !== newStyle.width ||
hei !== newStyle.height)
fun();
wid = newStyle.width;
hei = newStyle.height;
id = requestAnimationFrame(test);
let test = document.querySelector('.test');
addResizeListener(test,function ()
console.log("I changed!!")
);
【讨论】:
【参考方案7】:例如当你制作时
var clonedForm = $('#empty_form_to_clone').clone(true)[0];
var newForm = $(clonedForm).html().replace(/__prefix__/g, next_index_id_form);
// next_index_id_form is just a integer
我在这里做什么? 我克隆了一个已经渲染的元素并更改了要渲染的 html。
接下来我将该文本附加到容器中。
$('#container_id').append(newForm);
当我想将事件处理程序添加到 newForm 内的按钮时,问题就来了,WELL,只需使用 ready 事件。
$(clonedForm).ready(function(event)
addEventHandlerToFormButton();
)
希望对你有所帮助。
PS:对不起我的英语。
【讨论】:
【参考方案8】:根据@Elliot B. 的回答,我制定了一个适合我的计划。
const callback = () =>
const el = document.querySelector('#a');
if (el)
observer.disconnect();
el.addEventListener('click', () => );
;
const observer = new MutationObserver(callback);
observer.observe(document.body, subtree: true, childList: true );
【讨论】:
以上是关于是否有“元素渲染”事件?的主要内容,如果未能解决你的问题,请参考以下文章