REMux: An Experimental Approach to Responsive Web Design

Avatar of Chris Coyier
Chris Coyier on (Updated on )

The following is a guest post by Dirk Lüth. Dirk had an interesting take on responsive layout. It uses media query breakpoints to shuffle things around as we are used to, but the fluid column widths are set in rem units rather than percentages, and the font-size is adjusted dynamically with JavaScript based on the screen width. The result is a bit of a “zooming” effect, up or down, until the next media query hits. I’ll let him tell you more about it.

Introduction

I’m sure we all agree that responsive web design has been one of the biggest subjects in the last few years and will continue with the growth of mobile. As a senior front and backend developer with a strong interest in research and development at my company, I am responsible to evaluate techniques like RWD. Whenever I received a link to a totally new CSS grid system, I became more and more skeptical. They did not feel “right” to me, but I wasn’t sure why.

Then I happened to come across a great article by Ian Yates titled “Life Beyond 960px: Designing for Large Screens” which introduced me to the term “Screen Real Estate”. Prior to that, I did some deeper research using rem units in CSS which was a fortunate coincidence. Suddenly I knew what felt wrong.

When talking about RWD we mostly talk about devices below the target width of our layouts. But what about larger screens? Most of you will agree that a non RWD website with a target width of 960px looks a bit odd or lost on such a screen. Things are becoming more obvious when we talk about people accessing our websites with a 60″ TV. Sure, these TV sets will most likely still only have full HD resolution. But keep in mind that whoever sits in front of them is probably at least 4m/10f away from the screen.

Current Situation

Whether we do mobile, tablet or desktop first – most of us will end up having at least 3 media query breakpoints with different layouts. Or we will use a grid system which will automagically change the composition of our sites’ content elements. Or a combination of both. Both approaches have their drawbacks if we are to support more and more different resolutions and different viewing situations:

  • More breakpoints = more layouts = more work
  • Hard to balance flexibility and proportions of elements
  • Jerky looking re-stacking of elements
  • Limited by the amount of content to fill the viewport
  • The art of the grid is more than some guides in Photoshop

And whatever way we choose to be most appropriate: the actual content (including every single element) will not scale proportionally.

Proof of Concept

What came to my mind was the idea of a solely rem-driven layout based on font-size. If this idea worked, I could easily scale all content by simply changing the font-size on the <html> element. This concept would solve the biggest challenge: layouts scale almost perfectly within their boundaries.

Keep in mind that rem is only supported in IE9+ and all other current browsers. For older browsers a px based fallback is possible. See the example mixins below.

I started to do some proof-of-concept work with my own site. The static version worked out really well after I had developed some LESS mixins that convert pixel units (taken directly from the layout) to rem units based on the 14px font-size I decided to use (see example).

LESS Mixins

There are two types of mixins here: one for single parameters properties (like font-size) and another one for properties with multiple parameters (like border). Both rely on two LESS variables that must be defined.

@remuxFontsize: 14px;  // base font size
@remuxFallback: false; // switch to include px fallback

.remux(font-size, @value) {
  @processingRem: ((round((@value / @fontsize-base) * 10000000)) / 10000000);

  .result(@value, @fallback) when (@fallback = true) {
    font-size: ~"@{value}px";
    font-size: ~"@{processingRem}rem";
  }
  .result(@value, @fallback) when (@fallback = false) {
    font-size: ~"@{processingRem}rem";
  }

  .result(@value, @remuxFallback);
}

.remux(border, @value) {
  @processingRem: ~`(function() { var base = parseFloat("@{remuxFontsize}"); return "@{value}".replace(/[\[\]]/g, '').replace(/([0-9]*[.]{0,1}[0-9]*px)/gi, function() { return ((Math.round((parseFloat(arguments[0]) / base) * 10000000)) / 10000000) + 'rem'; }); }())`;

  .result(true) {
    border: @value;
    border: @processingRem;
  }
  .result(false) {
    border: @processingRem;
  }

  .result(@remuxFallback);
}

Using the mixins is quite simple:

/* Instead of this... */
font-size: 16px;
/* You use this */
.remux(font-size, 16px);

/* Instead of this... */
border: 7px solid #000;
/* You use this */
.remux(border, ~"7px solid #000");

When I switched my site’s CSS to use rem I was already able to change the font-size of the <html> element via the developer tools and see the site scale almost perfectly!

What I needed next was a solution to calculate this dynamically via JavaScript on “resize” and “orientationchange” events. The basic calculation is really simple:

;(function(window, document, undefined) {
  'use strict';
  
  var targetLayoutWidth = 980,
    baseFontsize      = 14,
    htmlElement       = document.getElementsByTagName('html')[0],
    documentElement   = document.documentElement;
  
  function updateFontsize() {
    var currentFontsize = baseFontsize * (documentElement.offsetWidth / targetLayoutWidth);
      
    htmlElement.style.fontSize = currentFontsize + 'px';
  }
    
  window.addEventListener('resize', updateFontsize, false);
  window.addEventListener('orientationchange', updateFontsize, false);
  
  updateFontsize();
}(window, document));

When setting the result font-size, I realized that floating point numbers cause problems when rounded by the browser (see example). So I ended up having to floor the font-size which eliminated any rounding glitches but made the scaling less “fluid”. It still feels more than sufficient for me though (see example).

Next I did some extended testing on my iPad 2 with the HTML viewport set to “device-width”. I admit having been a bit surprised when not only everything worked after the initial page load but also pinch-to-zoom and changes in device orientation behaved as desired.

Implementing Layouts

Now that I was able to infinitely scale my layout I started implementing breakpoints. Breakpoints will still be required because scaling the font-size down to zero or up to infinity does not really make sense (although technically possible).

I started planning the structure of my future layout object by determining what was required. That lead to the following JavaScript object structure:

var layouts = {
  'ID': {
    width:      980,            // designs target width
    base:       14,             // designs base font-size
    min:        10,             // minimum font-size
    max:        18,             // maximum font-size
    breakpoint: 980 * (10 / 14) // minimum breakpoint
  }
}

Now that layouts could be defined I had to add them to the update function which had some more impact than I had thought (see example):

;(function(window, document, undefined) {
  'use strict';

  var htmlElement     = document.getElementsByTagName('html')[0],
    documentElement = document.documentElement,
    layouts = {
      mobile: {
        width:      420,
        base:       14,
        min:        10,
        max:        23,
        breakpoint: 420 * (10 / 14)
      },
      desktop: {
        width:      980,
        base:       14,
        min:        10,
        max:        18,
        breakpoint: 980 * (10 / 14)
      }
    },
    state = {
      size:   null,
      layout: null
    };

  function updateFontsize() {
    var width, id, layout, current, size;
  
    width = documentElement.offsetWidth;
  
    for(id in layouts) {
      if(layouts[id].breakpoint && width >= layouts[id].breakpoint) {
        layout = id;
      }
    }
  
    if(layout !== state.layout) {
      state.layout = layout;
  
      htmlElement.setAttribute('data-layout', layout);
    }
  
    current = layouts[state.layout];
  
    size = Math.max(current.min, Math.min(current.max, Math.floor(current.base * (width / current.width))));
  
    if(size !== state.size) {
      state.size = size;
  
      htmlElement.style.fontSize = size + 'px';
    }
  }
  
  window.addEventListener('resize', updateFontsize, false);
  window.addEventListener('orientationchange', updateFontsize, false);
  
  updateFontsize();
}(window, document));

I tested this prototype on Chrome, Safari, FF17+ and IE9 (where REMux should work), and it does work.

See it in Action

already mentioned that REMux is heavily (ab)used on my own website. Beside that, there are some Pens up on CodePen which were linked throughout the article. In addition there is another Pen showing what I ended up with:


I hope to get some documentation ready soon – but the code is not that hard to read as it is.

What’s Next?

REMux works so well that I decided to make it part of my growing JavaScript library which can be found on GitHub. Within the last couple of weeks I also added some more features that are missing in this basic article:

  • AMD compatible (require.js)
  • addLayout() method to add layouts without altering the script
  • Ratios for pixel density, zoom level detection, font-size, total and images
  • getState() method that will return all sizes and ratios
  • emits events ratiochange and layoutchange with the current state passed as argument
  • Flood protection mechanism for repeated input events for better performance
  • Extendable by an object extension mechanism

The standalone version of REMux can be found in the “packages” folder of my GitHub repository and weighs about 4kb minified and gzipped. It does not depend on jQuery or any other bigger library. Its dependencies to other components of my library are all included in the package. All components of my JavaScript library are dual licensed under the MIT and GPL license.

Feel free to download and use it in your projects but remember to give feedback and report bugs so I can make it even better!