1

考虑以下test-elementWeb 组件定义(在 Google Chrome 上运行的原生 JavaScript,而不是 Polymer),它创建了一个简单的width=500px. 它的attachedCallback函数将其宽度输出到控制台,然后设置一个异步延迟再次执行此操作:

测试元素.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,并再次从内联脚本标签输出其宽度:

索引.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 像素,我可以 make testElement.style.width = "100px",并且我不必担心组件在回调有机会进行更改之前闪烁其先前的 500px 宽度。

但是,attachedCallback有时在布局之前被调用并不是我想要的。例如,如果test-element包含一个网格,并且它需要绘制单元格,那么我需要知道它的宽度,以便我可以计算有多少单元格适合可用空间。但是我在 期间没有这些信息attachedCallback,我似乎无法掌握如何获取这些信息。

如您所见,在这个非常简单的示例中,0ms 延迟 (2) 似乎触发了布局计算。但是一旦我开始做更复杂的事情(例如当我有嵌套的 Web 组件和异步 HTML 导入时),0ms 延迟是不够的,我必须将其更改为 100ms 延迟,然后更多。

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

4

2 回答 2

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 ),我并不完美了解情况以绝对确定此解决方案。

于 2015-11-11T21:19:07.037 回答
0

在上面的示例中,在元素之后的标记中获取(自定义)元素的最快方法,正是您在测试中所做的 (3):width<script>

<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 来了解自己:网格元素还不知道它width在步骤 (4) 中的渲染。

(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);
test-element {
  display: inline-block;
  border: 1px solid red;
  width: 500px;
}

.large {
  width: 300px;
}
<!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>

于 2015-11-11T18:38:35.857 回答