/ Javascript

Javascript: Elements resize detection

I am sure you have gotten stuck during your web application development but, how many times wondering how to detect when a simple HTML element changes its dimensions? Well, it is not a simple task and accepts many different paths to a solution, therefore the one which provides the best solution in terms of reusability, performance, compatibility, and code legibility really requires a thorough research.

If it is that hard, is the effort worth it?

Totally, and I will expose all the use cases where this is a better option than going around the problem:

Handling your page responsive design

It is really important to make applications react to the screen resolution changes as well as making them consistent on every platform even on different touch supported devices. Web surveys speak for themselves:

  • In 2018 (based on Hootsuite and We Are Social reports) the 52% of web pages were served to mobile browsers, 43% to laptop's and desktop's, 4% to tablet's, and 0.14% to others'.

Maybe some web developers get along with programming a different application version for every device screen resolution range. Unfortunately, I have bad news for them:

  • Around the 16% of users accessing websites trigger a window resize. It is not a high number but in terms of potential users, we are talking about thousands of sessions.

Making elements respond to the layout dynamic changes

Another problem comes when you want to detect whether or not your layout elements have been altered by another element (not the window) when it gets into the play modifying the current structure or forcing their resizing. In this case, monitoring these variations becomes a nightmare since the most common detection techniques, like time interval functions, compromise the performance, and others like the window resize event listener does not cover the common cases at all.

The workaround

Here is a cross-browser element resize detection implementation explained in three steps. Almost entirely based on Daniel Buchner's post about this.

Create the "key" element

The <body> HTML tag is the only one supported by the onresize event which is triggered only when the window instance that "body" belongs to is resized. An instance of an HTML <object> element creates and embed environment with a specific document object instance within a window object. This way, it is possible to initialize a listener of that embed window resize event. So this object element is the key.

// The element we want to monitor
var element;
// Create the object child
var obj = document.createElement("object");
// Set it up as a standard object
obj.type = "text/html";
obj.data = "about:blank";

// Hide the object to not interfere with its parent layout
obj.setAttribute("style",
  "display: block; 
  position: absolute; \n\
  top: 0; 
  left: 0; 
  height: 100%; 
  width: 100%;
  overflow: hidden;
  pointer-events: none; 
  z-index: -1;"
);

// Append it and store its reference
element.__resizeElement = obj;
element.appendChild(obj);

Monitor the object element size

In this example, we are taking advantage of the window.requestAnimationFrame function, a powerful tool that delegates the page animation repaintings control to the browser. This is a good way to improve the elements resize process performance.

The following code provides a fallback in case the browser does not support the requestAnimationFrame:

var requestFrame = (function() {
    var raf =
      window.requestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      function(fn) {
        return window.setTimeout(fn, 20);
      };
    return function(fn) {
      return raf(fn);
    };
  })();
  
// Cancel animation callback update request
var cancelFrame = (function() {
    var cancel =
      window.cancelAnimationFrame ||
      window.mozCancelAnimationFrame ||
      window.webkitCancelAnimationFrame ||
      window.clearTimeout;
    return function(id) {
      return cancel(id);
    };
})();

Once your object is instantiated the next step is to create the listener to its resize event.

// Define the resize callback function
function resizeListener(e) {
  // Get the target window object
  var win = e.target || e.srcElement;
  // Cancel the current animation callback request 
  if (win.__resizeRAF) cancelFrame(win.__resizeRAF);
  // Starts the animation callback request
  win.__resizeRAF = requestFrame(function() {
     var trigger = win.__resizeTrigger;
     // Check this context window resize trigger element
     if (trigger) {
       // Call every monitored element resize callbacks
       for (var key in trigger.__resizeListeners) {
         trigger.__resizeListeners[key].call(trigger, e);
       }
     }
  });
}

// Wait until the object is loaded
obj.onload = function(){
    // ** Document.defaultView returns the window object related to this document scope
    var defaultView = this.contentDocument
      ? this.contentDocument.defaultView
      : this.contentWindow;
    // Stores the window resize monitor target into an embed window variable
    defaultView.__resizeTrigger = this.__resizeElement; 
   // Create the resize listener
    defaultView.addEventListener("resize", resizeListener);
};

Browser compatibility

Old IE browser versions (<10.0) have no window.requestAnimationFrame support. As a consequence, this example uses a 20 milliseconds delay between each resize operation releasing the browser from part of the rendering work:

var requestFrame = (function() {
    // IE rendering improvement
    var raf = function(fn) {
        return window.setTimeout(fn, 20);
      };
    return function(fn) {
      return raf(fn);
    };
 })();

Bearing in mind that until IE 9.0 version this browser does not support addEventListener either, it is necessary the use of attachEvent when declaring the the resize listener instead. So, at the beginning of the code:

// IE related 'addEventListener'
var attachEvent = document.attachEvent;

if (attachEvent) {
    // No extra code is necessary since old IE versions support `onresize` on any element
    element.__resizeTrigger = element;
    element.attachEvent("onresize", resizeListener);
} else {
    // Modern browsers implementation
    // ...
}

When will it become a standard?

Currently W3C is working on a new implementation to allow any HTML element to be notified of resize changes. This new tool is called Resize Observer and it was proposed in 2017 by this organism. It seems like the community is heading on the right path but, until a new standard is reached, the solution provided in this article could help you overcome this programming obstacle.