Wednesday, March 19, 2014

Continuously Reactive Web Page

I recently created an HTML5 application that was a slide presentation, like PowerPoint. You might be asking, why not use PowerPoint then? Well, because my presentation was on HTML5 so I wanted to write it in HTML5 to show as an example. Plus I like writing HTML5 apps.

Anyway, one of the points of my presentation was that HTML5 apps can run on any device no matter what screen size. So I had to make it reactive so that it would look good whether viewed on desktop or phone. My first thought was to use CSS media queries which is the usual way to make reactive layouts. Unfortunately there were dead zones in my media queries where it just didn't look right. I had queries for a lot of different screen sizes but it wasn't enough. Also I wanted to maintain the correct width to height ratio no matter what the size.

Finally I came up with a pretty cool solution that didn't involve media queries. This solution uses JavaScript and font sizes in CSS. What I found is that if you set the font size of an element using EMs then all of the sizes specified using EMs below that will be relative to the parent's size. For example, say 1em is the same as 16 pixels (the default in most browsers). If I have a parent element and I set the font size of it to 2em then the text will be 32 pixels. Now if I set the font size of a child element to 2em what size will it be? No, not 32 pixels. It will actually show up on the page as 64 pixels. Why? Because the child's font size is multiplied by the parent's font size. So in this case 2em * 2em = 4em which is 4 * 16 = 64 pixels.

The fun doesn't end there. You can specify the size of anything in CSS using EMs, including elements. Those sizes will also be relative to the font size. So if I set an element's font size to 2em and the width to 20em then the size will be 2em * 20em = 40em which is 40 * 16 = 640 pixels. This fact allows us to easily create a continuously reactive layout in which no matter how large or small the screen size it will always look the same.

Here's how it's done. Let's say we have HTML that look something like this. The slide element will be the content element and the one that needs to resize when the browser size changes.

    <div class="wrapper">
        <div class="slide">

First we set the font size of the body to 16px just to make sure it's always the same in each browser.

    font: 16px Times, serif;

Then we use EMs to define the size of everything else, from element width and height to border sizes to margins. For example we set the size of our slide element using EMs.

    width: 40em;
    height: 30em;
    padding: 1em;

Then we can change the font size of the wrapper element to adjust the size of the slide element. By default the width of the slide is 40 * 16 = 640 pixels. The height is 30 * 16 = 480 pixels. Now if we change the wrapper element's font size to 2em then the width of the slide element is 2 * 40 * 16 = 1280 pixels. If we change it to 0.5em then the width is 0.5 * 40 * 16 = 320 pixels.

The tricky part is figuring out what font size to set the wrapper element to. That requires some JavaScript. In our JavaScript we need to get the default width, height and ratio of the slide element before we do any resizing. This gets the values in pixels.

var defaultWidth = $(".slide").width(),
    defaultHeight = $(".slide").height(),
    defaultRatio = defaultHeight / defaultWidth;

Next we need to add a window resize event handler. In there we will write the code that adjusts the wrapper element's font size.


And here is the onResize function.

function onResize()
    var fontSize = 0;
    if (window.innerWidth * defaultRatio > window.innerHeight)
        fontSize = window.innerHeight / defaultHeight;
        fontSize = window.innerWidth / defaultWidth;
    $(".wrapper").css("font-size", fontSize + "em");

First thing we do is figure out if the browser window is taller or wider compared to the slide's ratio of with to height. If it's wider then we need to adjust the size depending on the height of the window. If it's higher then we adjust the size depending on the width of the window. Dividing by our default slide width or height we get the factor that determines how much the slide element needs to be scaled to fit the window. Now all we need to do is set the font size of the wrapper element and like magic the slide element is scaled to the correct size.

Here's the HTML5 presentation that uses this technique to keep the slide always the correct size. Try changing the window size and the slide will always look the same.