79

我需要在我的网络应用程序中准确测量文本的尺寸,我通过创建一个元素(使用相关的 CSS 类)、设置它innerHTML然后使用appendChild.

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

目前,我一直setTimeout(processText, 100)在等待渲染完成。

有没有我可以听的回调,或者更可靠的方式来告诉我创建的元素何时被渲染?

4

8 回答 8

71

接受的答案来自 2014 年,现已过时。AsetTimeout可能有效,但它不是最干净的,也不一定保证元素已添加到 DOM。

截至 2018 年,您应该使用MutationObserver来检测元素何时添加到 DOM。现在,所有现代浏览器(Chrome 26+、Firefox 14+、IE11、Edge、Opera 15+ 等)都广泛支持 MutationObservers。

将元素添加到 DOM 后,您将能够检索其实际尺寸。

这是一个简单的示例,说明如何使用 aMutationObserver来侦听何时将元素添加到 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.

您不需要遍历存储的每个 MutationRecord,mutations因为您可以document.contains直接在myElement.

要提高性能,请替换document为将包含myElement在 DOM 中的特定元素。

于 2018-01-30T06:04:52.057 回答
58

当前没有 DOM 事件指示元素已完全呈现(例如,附加的 CSS 应用和绘制)。这会使一些 DOM 操作代码返回错误或随机的结果(例如获取元素的高度)。

使用 setTimeout 给浏览器一些渲染开销是最简单的方法。使用

setTimeout(function(){}, 0)

可能是最实际准确的,因为它将您的代码放在活动浏览器事件队列的末尾而没有任何延迟 - 换句话说,您的代码在渲染操作(以及当时发生的所有其他操作)之后立即排队。

于 2014-01-10T11:21:10.823 回答
18

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)) 在元素被渲染之后也是如此。

于 2016-06-25T13:33:15.983 回答
9

MutationObserver 可能是最好的方法,但这里有一个可能有效的简单替代方法

我有一些 javascript 为大表构建 HTML,并将 div 的 innerHTML 设置为生成的 HTML。如果我Date()在设置 innerHTML 后立即获取,我发现时间戳是在表格完全呈现之前的一段时间。我想知道渲染需要多长时间(这意味着我需要Date()在渲染完成后检查)。我发现我可以通过设置 div 的 innerHTML 然后(在同一个脚本中)调用页面上某个按钮的 click 方法来做到这一点。只有在 HTML 完全呈现后才会执行单击处理程序,而不仅仅是在设置 div 的 innerHTML 属性之后。我通过将点击处理程序生成的 Date() 值与设置 div 的 innerHTML 属性的脚本检索到的 Date() 值进行比较来验证这一点。

希望有人觉得这很有用

于 2018-04-05T02:55:47.253 回答
3

在我的情况下,解决方案就像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 ...
});

于 2021-02-12T12:37:34.913 回答
1

假设你的元素有 classname 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!!")
        });
于 2019-02-27T23:02:16.187 回答
0

例如,当您制作时

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内的按钮时,问题就来了,只需使用准备好的事件。

$(clonedForm).ready(function(event){
   addEventHandlerToFormButton();
})

我希望这对你有帮助。

PS:对不起我的英语。

于 2016-09-28T13:54:43.243 回答
0

根据@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 });
于 2020-10-26T02:35:19.417 回答