Web组件如何尽快获得自己的样式(宽度等)?

时间:2015-11-09 03:49:00

标签: javascript web-component custom-element html-imports

考虑以下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延迟,然后更多。

我的问题是:如何尽快可靠地获取组件(或任何其他计算样式)的宽度?

2 个答案:

答案 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

&#13;
&#13;
(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;
&#13;
&#13;