Flexbox Based Responsive Equal Height Blocks With JavaScript Fallback

After I’ve published the post on how I had implemented responsive equal height blocks into Readerrr, I received some useful feedback from the community. Daniel Sturm suggested using CSS3’s Flexbox rather than JavaScript, and Veerle Pieters tweeted “<...> you could do with Flexbox & use this as JS fallback”. Exactly! How did I not think of that at all?! I’ve read several articles about Flexbox before but never tried it myself, therefore this thing totally went out of my head.

Why Flexbox? In short, the Flexbox Layout module was designed to solve problems exactly like this. It is an efficient and flexible way to manage probably all types of layouts. It provides almost no time gap between initially wrong and correctly laid out layout look. In JavaScript solution case it takes time to download the document, then to download the corresponding JS file, and, if there are any, download images in the blocks. Flexbox is instant and JavaScript takes seconds. Even so, this JavaScript case is perfect for people who use older browsers that do not support Flexbox.

The problem

If you haven’t read my previous post, you don’t need to. Here’s the code and the problem (broken-like layout) to solve:

<ul class="list">
	<li class="list__item"><!-- content --></li>
	<li class="list__item"><!-- content --></li>
	<!-- other items -->
</ul>
.list {
	overflow: hidden; /* just clearing floats */
}

.list__item {
  width: 25%; /* 4 items per row */
  float: left;
}

Flexbox Based Responsive Equal Height Blocks With JavaScript Fallback

The solution

If you haven’t had any touch with Flexbox, you’ll be surprised how magical it is. display: flex initiates Flexbox for the container element and flex-wrap: wrap tells to wrap child items rather than fit them onto one line. Repeating display: flex for the child items makes sure the elements have the same heights in their rows.

.list {
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;

	-webkit-flex-wrap: wrap;
	-ms-flex-wrap: wrap;
	flex-wrap: wrap;
}

.list__item {
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

Flexbox Based Responsive Equal Height Blocks With JavaScript Fallback

This works perfectly in the latest versions of Chrome, Android, Safari, Opera, Firefox, and Internet Explorer 10+. For the rest I have a JavaScript pill.

I did not include this into the previous CSS code, but some of the older WebKit browsers support the old Flexbox syntax (display: -webkit-box). However, the wrapper property -webkit-box-lines: multiple simply does not work on iOS Safari 6.1- nor on Android 4.3-.

JavaScript fallback

This part covers a fallback solution for such browsers as Internet Explorer 9-, Android 4.3-, iOS Safari 6.1-, and Opera Mini. I wrote a tiny piece of jQuery-dependent code which:

  1. Detects if the browser does not support Flexbox;
  2. Calculates the number of items per row by dividing the widths of .list and .list__item;
  3. Virtually divides the list into rows accordingly to that number;
  4. Detects which item has the biggest height in each row;
  5. Sets these heights for other items in each row correspondingly.
;(function($, window, document, undefined) {
	var s = document.body || document.documentElement, s = s.style;
	if(s.webkitFlexWrap == '' || s.msFlexWrap == '' || s.flexWrap == '') return true;

	var $list = $('.list'),
		  $items = $list.find('.list__item'),
		  setHeights	= function() {
        $items.css('height', 'auto');

        var perRow = Math.floor($list.width() / $items.width());
        if(perRow == null || perRow < 2) return true;

        for(var i = 0, j = $items.length; i < j; i += perRow) {
          var maxHeight	= 0,
              $row = $items.slice(i, i + perRow);

          $row.each(function() {
            var itemHeight = parseInt($( this ).outerHeight());
            if (itemHeight > maxHeight) maxHeight = itemHeight;
          });
          $row.css('height', maxHeight);
        }
      };

	setHeights();
	$(window).on('resize', setHeights);
	$list.find('img').on('load', setHeights);

})(jQuery, window, document);

What if JavaScript is disabled in a browser? The problem is that CSS’s native feature detection is less supported than Flexbox itself. Therefore, using @support rule will not cover every browser that supports Flexbox. But that’s better than nothing.

What I suggest is to treat things this way: disabled JavaScript = no Flexbox support (I believe this equality is mostly correct) and fix the exceptions with the help of @support. Technically, add a class name .no-js to <html> tag and remove it with JavaScript. That’s how we’ll know if it is disabled or not. Then, style the list items respectively and finally “compensate” this styling with the help of @supports.

I chose to present the blocks as full-width rows in this case. If there are any images, they will be aligned by the right edge of the row on larger screens.

<html class="no-js">
	<head>
		<!-- your stuff -->
		<script>(function(e,t,n){var r=e.querySelectorAll("html")[0];r.className=r.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")})(document,window,0);</script><!-- remove this if you are using Modernizr -->
	</head>
	<!-- your stuff -->
</html>
html.no-js .list__item {
	width: 100%;
	float: none;
}

html.no-js .list__item img {
  max-width: 9.375rem; /* 150 */
  float: right;
  margin-left: 1.25rem; /* 20 */
}

@supports (display: -webkit-flex) or (display: flex) {
	html.no-js .list__item {
		width: 25%;
		float: left;
	}

  html.no-js .list__item img {
    max-width: none;
    float: none;
    margin-left: 0;
  }
}

Responsive Equal Height Blocks

Demo

See the demo.

&