Javascript Layout Hacks
22 February 2017
Sometimes CSS falls short in achieving the desired layout. I've recently refactored a handful of Javascript Layout Hacks I've regularly re-used as CommonJS modules and made them available via NPM. All of the modules here address problems address problems of dynamic layout, where things need to adjust both to changes in content and changes in window size, and where css rules alone can't account for all these various permutations.
The problem
It started with a common design pattern that had no good CSS solution (until flexbox came along): creating multiple boxes of equal height and responsive width, when the content (usually text) within the boxes is not the same size. One can set a fixed height for all, but this can lead to a lot of empty space as the boxes get wider, or a whole lot of media queries to keep it tight at all sizes. The latter can be acceptable for static content, but when you have dynamic content you either have to set strict guidelines for content length or try to find some compromise setting that works within acceptable margins for most possible scenarios. This gets ugly fast, involves too much tweaking and is subject to breaking when the content changes.
Dynamically sized boxes of equal height
Today this is possible with CSS alone via flexbox layouts, but when I first tried to solve this problem flexbox wasn't widely implemented enough to use for a production site. So I wrote a little jQuery snippet that measured the height of each element in the set and then set all of them to the highest measured value. Of course, this value only worked for the screen size at the time of the measurement, so on resize events I had to have the snippet set the height attribute back to auto
, measure again and then set a new value.
Though in many cases I'd use flexbox to solve this problem now, the flexbox solution depends on the box elements being siblings. Though less common, there may be situations where it's necessary to make non-sibling elements the same height. A javascript solution makes that possible.
In many cases these equal height boxes were only necessary on wider screens, where they would be displayed side-by-side, and only served to add empty space on smaller screens where they would be in a single column. When copying and pasting in my jQuery snippet I would make the size assignment function conditional on a media rule condition, which I would hard-code on a per project basis.
That jQuery snippet was the foundation of the unit-tested CommonJS module I now have today, and the basis for the other modules I discuss here. The media rule can now be provided as an argument to this modules init
method.
Links
Dynamically sized elements of equal width
The code for making elements the same height can be pretty easily adapted for making elements the same width, though the use case is different and probably not as common. Where I have encountered this problem is in creating responsive layouts for tabular data. There are css solutions to this but in my mind they're not great.
The table
element is great for displaying tabular data, but not so great for responsive layouts. Sometimes nested ul
elements work better because they can allow a single "row" to wrap. The layout can be standardized by assigning a fixed width to the elements which comprise each column within this nested list structure, but as with the same-height problem a fixed width isn't always well adapted to dynamic contents and variable screen sizes.
Using my same-width module a column in a nested list table (or any other set of elements for use cases I haven't imagined) can be assigned to a standard width that is dependent on the content within it.
Links
Text alignment in responsive tables
Dynamically setting column widths for nested list tables goes a long way towards making responsive tables work but one issue that always irked me was text alignment. Columns on the left end of a table usually look better with text-aligned left and columns on the right end often look better with the text aligned right, but in a table where a single row might wrap onto a second line the same column might be on the right hand side at one screen width and on the left hand side with another.
To solve this issue, I wrote a module which detects whether an element's children are closer to its left hand side or its right and sets their text alignment accordingly. If a tolerance value in pixels is passed as a second parameter to its init
method, all elements further from either side than that tolerance value will be text-aligned center.
Links
Avoiding race conditions and adding additional triggers
When I first attempted to create a responsive table using the above two tricks I ran into a problem. On page load and on window resize events the widths were being measured and set simultaneously with the alignments being measured and set. Since the newly assigned widths could affect the position of an element I had a race condition that sometimes broke the text alignments.
The solution was to create a queue that all of the above modules would make use of. Actions would be executed by the queue on page load and on resize, but they would be executed sequentially in the order in which they were assigned. Thus, initializing the same-width module before initializing the align-to-sides module would eliminate the race condition and ensure proper text alignment.
One other issue I occasionally ran into was the issue of AJAX or other asynchronous content that could potentially load after one of the above layout tasks has completed. For this reason I added a method to trigger the queue at any given time. This method can be called on the loading of AJAX content or any other event specific to a given project.