">

is extremely useful in layout in modern web design. However, one thing it’s not good at vertically aligning wrapped text in some circumstances. Returning to the example I detailed last week, the solution works really well when the text for both links is the same length, but when the text of one link wraps but not the other, the result is out of alignment:

Vertically aligned text in seperate elements falling out of alignment with wrapping

While it’s a small issue, it’s also an annoying one, and one that I wanted to solve. The solution needed a little ; first, the markup:

<div id="prevnext" class="flex apart">  
    <a class="prev-one flex" href="/1059/Responsive-Image-Hinting-Using-the-w-Descriptor" rel="prev" accesskey=",">
        <span class="thumb">
            <img src="toubana-diver.jpg" alt srcset="toubana-diver-2x.jpg 2x">
        </span>
        <span class="articlename">
            Responsive Image Hinting: Using w
        </span>
    </a>
    <a class="next-one flex" href="/1063/Techniques-and-Treatments-for-Background-Retina-Images" rel="next" accesskey=".">
        <span class="articlename">
            Techniques and Treatments for Background Retina Images
        </span>
        <span class="thumb">
            <img src="birds-on-wire.png" alt srcset="birds-on-wire-2x.png 2x">
        </span>
    </a>
</div>

The is as before: see the last article for that.

The JavaScript

I’m going to make the assumption that this kind of navigation occurs only once per page, so I can identify each navigational element uniquely via a querySelector:

var leftText = document.querySelector("#prevnext a span.articlename"),
rightText = document.querySelector("#prevnext a:last-child span.articlename");

The adjustment of the text link elements is made in a function named matchHeight():

function matchHeight() {
    var difference = leftText.offsetHeight - rightText.offsetHeight;
    if (difference > 0) { 
        rightText.style.marginTop = "-" + difference+"px";
    }
  if (difference < 0) {
        leftText.style.marginTop = difference +"px"; 
  }
  if (difference == 0 && (leftText.hasAttribute("style") || rightText.hasAttribute("style"))) { 
      leftText.removeAttribute("style"); 
      rightText.removeAttribute("style");
  }
}

A quick explanation:

  • difference measures the height difference between the two text links. The result could be 0 (meaning that the links are the same height), positive (meaning the left link is taller than the right) or negative (i.e. the right link is taller than the left).
  • In the first case, the right link is raised (using a negative top margin) by the difference amount.
  • in the second case, the left link is raised by the difference.
  • in the last case, a 0 difference could be due to the fact that the links were originally the same height, or that they’ve been previously adjusted (raising one link), and are now the same height. I’ve added a test to see if either link has an inline style. If an inline style is present*, it’s removed, setting both links back into vertical alignment.

The function is called once on page load:

matchHeight();

The script needs to be called again whenever the link text wraps (changing the height of the element). It would be great if we could add an eventListener to do that, but JavaScript doesn’t currently detect height changes on elements. I’d love to be able to use a transition-triggered event like this one by David Walsh, but browsers don’t currently seem to detect that either, at least for flexbox elements. Instead, I resorted to calling the function efficiently on window resize with requestAnimationFrame:

window.addEventListener("resize", function() {
        window.requestAnimationFrame(matchHeight);
});

Accounting For Layout Delay

One curious problem I encountered in Chrome is that the browser would correctly align the text during initial page load, but not on refresh: in fact, it would report both elements as being the same height even if they were not. I suspect this is due to inefficiencies in browser layout: Chrome appears to be providing the elements with the same height during its first rendering pass after refresh, then refining the layout a little while later.

Treating this problem proved frustrating: a setTimeout did not work predictably; neither did an eventListener for DOMContentLoaded. JQuery's window load event was the only one that appeared to work predictably:

$(window).bind("load", function() {
   matchHeight();
});

You can see the result in the example at the top of this article; you can also expect to see it applied to this site in the very near future.

* I’m assuming that the only inline style applied to either link would be due to this script.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/rLpomz