考虑以下test-element
网络组件定义(在Google Chrome上运行的vanilla JavaScript,而非Polymer)
用width=500px
创建一个简单的组件。它的attachedCallback
函数将其宽度输出到控制台,然后设置异步延迟以再次执行:
测试element.html
<style> test-element { display: inline-block; width: 500px; } </style>
<script>
(function (window, document) {
var proto = Object.create(HTMLElement.prototype);
proto.attachedCallback = function () {
// Direct output.
console.log("(1) test-element.width = ", this.offsetWidth);
// Delayed output.
window.setTimeout(function () { console.log("(2) test-element.width = ", this.offsetWidth); }.bind(this), 0);
};
document.registerElement('test-element', {prototype: proto});
})(window, document);
</script>
然后我创建了一个导入test-element
的页面,并再次从内联脚本标记输出其宽度:
的index.html
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="test-element.html">
</head>
<body>
<test-element></test-element>
<script>
var testElement = document.querySelector('test-element');
console.log("(3) test-element.width = ", testElement.offsetWidth);
</script>
</body>
</html>
控制台的输出是:
(1) test-element.width = 0
(3) test-element.width = 500
(2) test-element.width = 500
这证明在布局和绘画之前调用attachedCallback
(1),这有时候就是我想要的。
例如,如果我想将其宽度更改为100像素,我可以制作testElement.style.width = "100px"
,我也不用担心
在回调有机会进行更改之前,组件会闪烁其先前的500px宽度。
但是,在布局之前调用attachedCallback
有时并不是我想要的。
例如,如果test-element
包含一个网格,并且需要绘制单元格,那么我需要知道它的宽度,以便我可以计算出多少个单元格
适合可用空间。但是我在attachedCallback
期间没有这些信息,我似乎无法掌握如何获取这些信息。
正如您所看到的,在这个非常简单的示例中,0ms延迟(2)似乎触发了布局计算。 但是一旦我开始做更复杂的事情(例如,当我有嵌套的Web组件和异步HTML导入时),0ms延迟是不够的,我必须将其更改为100ms延迟,然后更多。
我的问题是:如何尽快可靠地获取组件(或任何其他计算样式)的宽度?
答案 0 :(得分:3)
我似乎找到了解决方案,使用requestAnimationFrame
:
proto.attachedCallback = function () {
// This never works:
console.log("(1) test-element.width = ", this.offsetWidth);
// This works sometimes:
window.setTimeout(function () { console.log("(2) test-element.width = ", this.offsetWidth); }.bind(this), 0);
// SOLUTION - This seems to always work.
window.requestAnimationFrame(function () { console.log("(4) test-element.width = ", this.offsetWidth); }.bind(this));
};
注意:到目前为止,无论我嵌套了多少个Web组件,无论我将HTML导入链接得多么复杂,这对我来说都是可行的。但是......这总能奏效吗? HTML导入的HTML解析是并行化的(根据用户https://stackoverflow.com/users/274673/ebidel这里:http://www.html5rocks.com/en/tutorials/webcomponents/imports)并且我不完全理解这种解决方案绝对确定的情况。
答案 1 :(得分:0)
在上面的示例中,最快方式在元素之后的width
标记中获取(自定义)元素<script>
,这正是您在测试中所做的(3):
<test-element></test-element>
<script>
var testElement = document.querySelector('test-element');
console.log("(3) test-element.width = ", testElement.offsetWidth);
</script>
这是因为HTML元素是由浏览器按顺序呈现的。因此,脚本内容将在计算<test-element>
width
和其他布局属性后立即执行。
(1) test-element.width = 0
(3) test-element.width = 500
HTMLImportsLoaded //with polyfill
DomContentLoaded
window.onload
(2) test-element.width = 500
WebComponentsReady //with polyfill
注意:除Chrome之外的其他浏览器的行为也不同。
<强>更新强>
注意不要在渲染中过早地获取计算出的样式值,特别是如果另一个元素也使用requestAnimationFrame
方法。
下面的示例显示了一个Web组件并不总是依赖于这个hack来了解自己: grid 元素在步骤(4)中还不知道它呈现的width
。
(function(window, document) {
var proto = Object.create(HTMLElement.prototype);
proto.attachedCallback = function() {
// Direct output.
console.log("(1) %s.width = ", this.id, this.offsetWidth);
this.innerHTML = "Element: " + this.id
// Delayed output.
window.setTimeout(function() {
console.log("(2) %s.width = %s ", this.id, this.offsetWidth);
}.bind(this), 0);
window.requestAnimationFrame(function() {
if (this.id == "side")
this.classList.add("large")
console.log("(4) %s.width = %s", this.id, this.offsetWidth);
}.bind(this));
};
document.registerElement('test-element', {
prototype: proto
});
})(window, document);
&#13;
test-element {
display: inline-block;
border: 1px solid red;
width: 500px;
}
.large {
width: 300px;
}
&#13;
<!DOCTYPE html>
<html>
<head>
</head>
<body style="display:flex">
<test-element id="grid"></test-element>
<test-element id="side"></test-element>
<script>
var testElement = document.querySelector('test-element');
console.log("(3) %s.width = %s", testElement.id, testElement.offsetWidth);
window.onload = function() {
console.log("window.onload")
}
</script>
</body>
</html>
&#13;