【问题标题】:Is there 'element rendered' event?是否有“元素渲染”事件?
【发布时间】:2013-03-30 07:12:41
【问题描述】:

我需要在我的网络应用程序中准确测量文本的尺寸,我通过创建一个元素(使用相关的 CSS 类)、设置其 innerHTML 然后使用 appendChild 将其添加到容器中来实现。

完成此操作后,在渲染元素之前有一个等待,并且可以读取其offsetWidth 以了解文本的宽度。

目前,我正在使用setTimeout(processText, 100) 等待渲染完成。

是否有任何我可以收听的回调,或者更可靠的方式来判断我创建的元素何时被渲染?

【问题讨论】:

标签: javascript dom


【解决方案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 });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-15
            • 2021-08-05
            • 1970-01-01
            • 2020-08-20
            • 1970-01-01
            • 2022-08-16
            相关资源
            最近更新 更多