How can a web-component get its own styles (width etc) as soon as possible?

三世轮回 提交于 2019-12-23 02:28:12

问题


Consider the following test-element web component definition (vanilla JavaScript running on Google Chrome, not Polymer), that creates a simple component with width=500px. Its attachedCallback function outputs its width to the console, and then sets up an asynchronous delay to do it again:

test-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>

I then create a page that imports test-element, and outputs its width yet again from an inline script tag:

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>

The output to the console is:

(1) test-element.width =  0
(3) test-element.width =  500
(2) test-element.width =  500

This goes to prove that attachedCallback (1) is called before layouts and paints, which sometimes is really what I want. For example, if I want to change its width to 100 pixels, I can make testElement.style.width = "100px", and I don't need not worry about the component flashing its previous 500px width before the callback has a chance to make the change.

However, attachedCallback being called before layouts sometimes is NOT what I want. For example, if test-element contains a grid, and it needs to draw cells, then I need to know its width so that I can calculate how many cells fit the available space. But I don't have this information during the attachedCallback, and I can't seem to grasp how to get this information.

As you can see, a 0ms delay (2) seems to trigger the layout calculation in this very simple example. But as soon as I start doing more complicated things (for example when I have nested web components and asynchronous HTML imports), the 0ms delay is not enough, and I have to change it to a 100ms delay, and then more.

My question is: How can I reliably get the width of the component (or any other computed styles), as soon as possible?


回答1:


I seem to have found the solution, using 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));
    };

Note: This is so far working OK for me, no matter how many web components I nest, and no matter how complicated I chain my HTML imports. However... does this always work? HTML parsing of HTML imports is parallelized (according to user https://stackoverflow.com/users/274673/ebidel here: http://www.html5rocks.com/en/tutorials/webcomponents/imports) and I don't perfectly understand the situation to be absolutely sure of this solution.




回答2:


In your example above, the fastest way to get the (custom) element width in a <script> tag just after the element, exactly what you did in your test (3):

<test-element></test-element>

<script>
    var testElement = document.querySelector('test-element');
    console.log("(3) test-element.width = ", testElement.offsetWidth);
</script>

It's because HTML elements are rendered sequencially by the browser. So the script content will be executed just after the calculation of the <test-element> width and other layout properties.

(1) test-element.width =  0    
(3) test-element.width =  500
HTMLImportsLoaded  //with polyfill
DomContentLoaded   
window.onload
(2) test-element.width =  500
WebComponentsReady //with polyfill 

NB: Other browser than Chrome will behave differently.

Update

Be careful not getting the computed style values too soon in the rendering, especially if another element is using the requestAnimationFrame method, too.

Below an example that shows a web component cannot always rely on this hack to know itself: the grid element doesn't know its rendered width at step (4) yet.

(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>


来源:https://stackoverflow.com/questions/33602247/how-can-a-web-component-get-its-own-styles-width-etc-as-soon-as-possible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!