Blog-Archiv

Sonntag, 25. Juni 2017

JS Layout Reality versus CSS Reqirement

The difference between attributes and properties does not look very clear from the JavaScript point of view. The property textField.value delivers the current user input, while textField.getAttribute('value') delivers just the initial value? On the Internet you find answers:

An HTML attribute is one item in the "attributes" property of the DOM-node.

Attributes and properties are connected in different ways. Find details in the w3c specification.

Similar it is for read/writable style properties like height and their read-only reflection offsetHeight or clientHeight. If you set style.height of an DOM element, that value will be reflected into offsetHeight as soon as you request that property. With one exception: there was an animation (transition) defined on that property. This Blog is about what happens then.

Example

Height em Seconds


height:
offsetHeight:

Set "Height" to 8 em, and then press "Set Height". This will trigger

grayRectangle.style.transition = "";
grayRectangle.style.height = 8+"em";

By the control output inside the gray rectangle you can see that grayRectangle.offsetHeight is set to the according pixel-height immediately.

Now set "Height" to 12 em, and then press "Animate Height". This will do

grayRectangle.style.transition = "height 2s";
grayRectangle.style.height = 12+"em";

A timer is used to track the changes in grayRectangle.offsetHeight. You see that the CSS height value is not immediately reflected into that DOM node property, instead it is subsequently updated by the animation.

Consequences

When you use animation on a layout property like width or height, CSS style requirements differ from DOM reality for a while. A geometry calculation done in that time will not be accurate when it uses DOM properties like offsetWidth or offsetHeight.

So be warned when you use geometry calculations like I introduced in my Blog about get and set on element width / height. They won't work when you use animations.

Workaround

In such a case you need to buffer all affected elements, set and get values in a custom-property on them, and provide a flush() function that you call at end of all layout calculations. Here is an example of what I mean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
      var bufferedHeights = [];
      
      var getOverallHeight = function(element) {
        if (element.deferredHeight === undefined) {
          element.deferredHeight = elementDimensions.getOverallHeight(element);
          addToBufferedHeights(element, false);
        }
        return element.deferredHeight;
      };
      
      var setOverallHeight = function(element, numericHeightValue) {
        element.deferredHeight = numericHeightValue;
        addToBufferedHeights(element, true);
      };
      
      var addToBufferedHeights = function(element, modified) {
        for (var i = 0; i < bufferedHeights.length; i++) {
          if (bufferedHeights[i].element === element) {
            if (modified) /* modification overwrites read-only access */
              bufferedHeights[i].modified = modified;
            
            return; /* already in list */
          }
        }

        bufferedHeights.push({
          element: element,
          modified: modified
        });
      };
      
      var flushBufferedHeights = function() {
        for (var i = 0; i < bufferedHeights.length; i++) {
          var bufferedHeight = bufferedHeights[i];
          
          if (bufferedHeight.modified)
            elementDimensions.setOverallHeight(bufferedHeight.element, bufferedHeight.element.deferredHeight);
          
          bufferedHeight.element.deferredHeight = undefined;
        }
        bufferedHeights = [];
      };

You can use getOverallHeight() and setOverallHeight() all the time during layout calculations. When all elements are sized, call flushBufferedHeights() to close the gap between CSS requirements and DOM reality. That way you work around both calculation mistakes and repaint / reflow problems.




Keine Kommentare: