Event listener for text container size

A few months ago, a member posted a question in the CSS category looking for a way to adjust the horizontal spacing of his menu. I just wrote a beginner’s jQuery script that seems to do the trick, but it needs the help of an event listener (I think) that will detect when the width of the <ul> changes (user changes font size maybe).

Is it possible to write an event listener (I guess that’s what it would be called) that would detect a change in the width of the <ul> and trigger a script? If so, I’ll post some example code in need of the listener/handler thingy and go from there.

Thanks!

Well, there sure are a lot of events

Maybe resize?

(function() {

  window.addEventListener("resize", resizeThrottler, false);

  var resizeTimeout;
  function resizeThrottler() {
    // ignore resize events as long as an actualResizeHandler execution is in the queue
    if ( !resizeTimeout ) {
      resizeTimeout = setTimeout(function() {
        resizeTimeout = null;
        actualResizeHandler();
     
       // The actualResizeHandler will execute at a rate of 15fps
       }, 66);
    }
  }

  function actualResizeHandler() {
    // handle the resize event
    ...
  }

}());
1 Like

I read the Mozilla list while searching for a solution and didn’t recognize anything that might work. Your example seems to effect the resizing of something but I can’t tell what triggers it. My need is to detect a change in the width of a container and trigger the script that I already wrote (which should work after the page is loaded). Detecting the change in width is what I don’t know how to do. With that, I’m well into my level of incompetence.

A resize event is when

The document view has been resized.

I don’t think there’s anything like that (that’s as simple) for individual page elements, only for the page as a whole.

If the page size is staying the same and the input is getting resized by “dragging”. More complex, but doable.

Nope, nothing to drag. Seems like it ought to be doable (wishful thinking), but so far no joy.

Maybe @James_Hibbard knows a trick?

You could probably determine the width of the element, store it in a variable, and then set a timer to regularly check the width to see if it’s changed, and if so, do something. Seems a bit cumbersome, though.

Hey Ron,

You could certainly do it as Ralph suggests.

Alternatively, you could use the method outlined here: [Cross-Browser, Event-based, Element Resize Detection][1]

I would prefer this, as there is no polling of the DOM involved.

Here’s an example of how you might implement it in your case:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>DOM Elements! Y U No Resize Event?</title>
    <style>ul{ background: red; }</style>
  </head>
  <body>
    <ul id="myList">
      <li>Hi</li>
    </ul>

    <script>
      (function(){
        var attachEvent = document.attachEvent;
        var isIE = navigator.userAgent.match(/Trident/);
        var requestFrame = (function(){
          var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
              function(fn){ return window.setTimeout(fn, 20); };
          return function(fn){ return raf(fn); };
        })();

        var cancelFrame = (function(){
          var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
                 window.clearTimeout;
          return function(id){ return cancel(id); };
        })();

        function resizeListener(e){
          var win = e.target || e.srcElement;
          if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
          win.__resizeRAF__ = requestFrame(function(){
            var trigger = win.__resizeTrigger__;
            trigger.__resizeListeners__.forEach(function(fn){
              fn.call(trigger, e);
            });
          });
        }

        function objectLoad(e){
          this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
          this.contentDocument.defaultView.addEventListener('resize', resizeListener);
        }

        window.addResizeListener = function(element, fn){
          if (!element.__resizeListeners__) {
            element.__resizeListeners__ = [];
            if (attachEvent) {
              element.__resizeTrigger__ = element;
              element.attachEvent('onresize', resizeListener);
            }
            else {
              if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
              var obj = element.__resizeTrigger__ = document.createElement('object');
              obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
              obj.__resizeElement__ = element;
              obj.onload = objectLoad;
              obj.type = 'text/html';
              if (isIE) element.appendChild(obj);
              obj.data = 'about:blank';
              if (!isIE) element.appendChild(obj);
            }
          }
          element.__resizeListeners__.push(fn);
        };

        window.removeResizeListener = function(element, fn){
          element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
          if (!element.__resizeListeners__.length) {
            if (attachEvent) element.detachEvent('onresize', resizeListener);
            else {
              element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
              element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
            }
          }
        }
      })();

      var myElement = document.getElementById('myList'),
      myResizeFn = function(){
        console.log("I was resized")
      };

      addResizeListener(myElement, myResizeFn);
    </script>
  </body>
</html>

I also had it in mind that you could use the [MutationObserver][2] API, but I couldn’t get this to work.

HTH
[1]: http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
[2]: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

1 Like

Wonders will never cease :slight_smile:

Hey Paul,

In what respect?
Surely not that I have a mind … ahem.

I like the MutationObserver for AJAX loaded changes, but darn can it be a memory hog.

lol. No we know you have a great mind:)

The mutationObserver sounded like someone who watches the Walking Dead.

Man, they’re on season 5!!
I gave up on it after episode 4 of season 1
Is it worth carrying on watching?

The MutationObserver API is actually great news and will help with things like two-way data binding in native JS.

I think it’s the best thing on TV (that and Game of thrones) .:slight_smile:

1 Like

Yes. 9/10

I binged on Z Nation on Netflix this weekend. About half the quality of TWD but still pretty good. (6.5-7/10 imo) It’s more fun an lighthearted than TWD.

But it’s a SyFy Original and I’ve never seen one of those go more than 2 seasons before getting insane and extremely low quality.

1 Like

I think this is perfect. I am so annoyed by hesitations in browsers that seeing “No polling of the DOM” is nothing short of over-the-top amazing!!! I’ll need to experiment with it for awhile to understand what I can do with it, but so far it rocks!

How do I code a line under the console.log that displays the width of “myList”?

How far back is this code compatible or is it? It looks cutting edge. What about cross-browser/device compatibility?

I’m not sure when I’ll be back but hopefully it will be with the sample menu and my few lines of jQuery code which I hope you will be in the mood to rewrite as real JS.

Looking at the conversation in this thread, I feel like I am off topic

Thanks bundles everyone. I’ll be baaaakkk

1 Like

Sorry Ron. My fault :slight_smile:

1 Like

Not at all!! I was enjoying the familiarity very much . The informality is refreshing .

1 Like

Within the callback, this will be the elemenet you attached the event handler to, so:

myResizeFn = function(){
  console.log(this.clientWidth);
};

I hooked that script to my little jQuery script and IT WORKS! :amazed:

Resizing the window width shows that the width of the <ul> (via the menu items) changes to keep the width of the <ul> within a 12px range overall (6px at each end) of the parent <div>. This resizing is expected to be short of smooth (slightly jumpy) because we only perform one quick math calculation to derive the menu width and then run one loop. More accuracy would be a little more complicated.

Resizing the font (which affects the width of the <ul>) is a lot jumpier. I would be very grateful if some kind person would translate my jQuery script into real JavaScript so we can find out if it makes a difference. Or maybe there are other techniques available to hide this jumpiness. However, even if the jumpiness cannot be hidden or abated, it shouldn’t be considered a real problem. Really. Who goes around dragging window widths and changing font sizes other than webbies?

Oh, swap the commented out lines in the jQuery with their most likely mate (the padd lines) and the width of the buttons becomes adaptable rather than the space between them (which becomes fixed).

The big downside? The menu items cannot wrap, so usefulness is limited to wider designs.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOM Elements! Y U No Resize Event?</title>
    <style>
body {
    font-size:100%;
    padding:0;
}
div {
    width:70%;
    background-color:red;
    border-radius:16px;
    padding:0 25px;
    margin:0 auto;
}
ul {
    list-style:none;
    display:table;
    border-spacing:0 0;
    word-spacing:-.3125em;
    white-space:nowrap;
    padding:0;
    margin:0 auto;
}
li {
    display:inline-block;
    height:52px;
    word-spacing:0;
    text-align:center;
    background-color:rgba(255,255,0 .4);
    outline:1px solid lime;
}
li:before {
    content:"";
    display:inline-block;
    height:100%;
    vertical-align:middle;
}
a {
    display:inline-block;
    vertical-align:middle;
    outline:1px solid blue;
    background-color:pink;
    padding:.125em 8px;
    margin:0 .75em;
}
    </style>
</head>
<body>

<div id="menuBox">
    <ul id="myList">
        <li class="buttonBox"><a href="#">Yo!</a></li>
        <li class="buttonBox"><a href="#">Hi There!</a></li>
        <li class="buttonBox"><a href="#">How About Today?</a></li>
        <li class="buttonBox"><a href="#">OK!</a></li>
        <li class="buttonBox"><a href="#">Another Day?</a></li>
        <li class="buttonBox"><a href="#">No.</a></li>
    </ul>
</div>

<script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script> 
<script>

(function(){
    var attachEvent = document.attachEvent;
    var isIE = navigator.userAgent.match(/Trident/);
    var requestFrame = (function(){
        var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
            function(fn){ return window.setTimeout(fn, 20); };
        return function(fn){ return raf(fn); };
    })();

    var cancelFrame = (function(){
        var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame ||
            window.clearTimeout;
        return function(id){ return cancel(id); };
    })();

    function resizeListener(e){
        var win = e.target || e.srcElement;
        if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__);
        win.__resizeRAF__ = requestFrame(function(){
            var trigger = win.__resizeTrigger__;
            trigger.__resizeListeners__.forEach(function(fn){
                fn.call(trigger, e);
            });
        });
    }

    function objectLoad(e){
        this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__;
        this.contentDocument.defaultView.addEventListener('resize', resizeListener);
    }

    window.addResizeListener = function(element, fn){
        if (!element.__resizeListeners__) {
            element.__resizeListeners__ = [];
            if (attachEvent) {
                element.__resizeTrigger__ = element;
                element.attachEvent('onresize', resizeListener);
            } else {
                if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
                var obj = element.__resizeTrigger__ = document.createElement('object');
                obj.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
                obj.__resizeElement__ = element;
                obj.onload = objectLoad;
                obj.type = 'text/html';
                if (isIE) element.appendChild(obj);
                obj.data = 'about:blank';
                if (!isIE) element.appendChild(obj);
            }
        }
        element.__resizeListeners__.push(fn);
    };

    window.removeResizeListener = function(element, fn){
        element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
        if (!element.__resizeListeners__.length) {
            if (attachEvent) element.detachEvent('onresize', resizeListener);
            else {
                element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener);
                element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
            }
        }
    }
})();


function fitMenu() {
    var buttons = $('.buttonBox a');         // # of button objects
    var margins = parseInt($(buttons[1]).css('margin-right'));
//    var padd = parseInt($(buttons[1]).css('padding-right'));
    var menuBox = $('div').width();
    var navWidth = $('#myList').width();
    var drift = 2*$(buttons).length;
    var diff = (menuBox - navWidth);
    if (diff < 0 || diff >= drift) {
        correction = Math.floor(diff/drift);
        margins = margins + correction;
//        padd = padd + correction;
        for (i = 0; i < buttons.length; i++) {
            $(buttons[i]).css('margin-left',margins);
            $(buttons[i]).css('margin-right',margins);
//            $(buttons[i]).css('padding-left',padd);
//            $(buttons[i]).css('padding-right',padd);
        };
    };
};


var myElement = document.getElementById('myList'),
myResizeFn = function(){
    fitMenu();
};
addResizeListener(myElement, myResizeFn);

window.onresize = fitMenu;
window.onload = fitMenu;

</script>
</body>
</html>

(the same file as the code above)
resizeEvent=Pullo2.html|attachment (5.3 KB)

Hi Ron,

So you want the fitMenu function translated to vanilla JS.
Did I get that right?