是否有“元素渲染”事件?

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(()=&gt;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】:

在我的情况下,setTimeoutMutationObserver 之类的解决方案并不完全可行。 相反,我使用了 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 );

【讨论】:

以上是关于是否有“元素渲染”事件?的主要内容,如果未能解决你的问题,请参考以下文章

eCharts源码浅读 - 初始化关键步骤

是否存在等于“点击”事件的指针事件?

vue中是否有必要使用事件委托

使用事件ID检查事件是否存在

是否有可能暂时禁用Laravel中的事件?

是否有任何“关于 DOM 更改”事件? [复制]