Scalable Web Design Part 2 - Liquid Design

Liquid Design: matching your website to the inner resolution of the browser.

Why "Liquid Design"?

The "Liquid" in this context refers to the ability of the web site to not only resize to various screen resolutions, but to also actively respond to the window that contains the site being resized. In essence, like any kind of liquid, it will adapt it's shape to that of which it lies within, i.e. the browser window. Click below for an example, and try resizing your browser window:

Liquid Layers Demo

The demo contains four layers, one of which contains an image. One of the main weaknesses of this technique is that sizing images correctly is not very accurate, you really cannot predict the user's browser window dimensions. You just have to go with it!

Each layer in the demo is positioned and scaled in proportion to the user's browser inner resolution, NOT their screen resolution as with fixed design. This will hopefully become clear when we next look at the entire code for the above example.

Coding for Liquid Design

Here is the code from the above demo in full:

<html>
<head>
<title>Liquid Design Demo</title>
 
<style type="text/css">
#red {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:2; 
background-color:red; visibility:hidden;}
#green {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:3; 
background-color:green; visibility:hidden;}
#blue {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:4; 
background-color:blue; visibility:hidden;}
#image {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:1; 
background-color:none; visibility:hidden;}
</style>
 
<script language="javascript">
//detects window inner resolution
function findLiveHeight(){
    if(window.innerHeight != null)
        return window.innerHeight;
    if (document.body.clientHeight != null)
        return document.body.clientHeight;
    return (null);
}
function findLiveWidth(){
    if(window.innerWidth != null)
        return window.innerWidth;
    if (document.body.clientWidth != null)
        return document.body.clientWidth;
    return (null);
}
</script>
 
<script language="javascript">
//makes dynamic layer
function makeLayer(id, position, left, top, width, height, visibility) {
    this.obj = document.getElementById(id).style;
    this.obj.position = position;
    this.obj.left = parseInt(left*xu);
    this.obj.top = parseInt(top*yu);
    this.obj.width = parseInt(width*xu);
    this.obj.height = parseInt(height*yu);
    this.obj.visibility = visibility;
    return this.obj;
}
</script>
 
<script language="javascript">
//this builds the individual layers
function build(){
    makeLayer('red','absolute','0','10','100','20','visible');
    makeLayer('green','absolute','25','30','20','20','visible');
    makeLayer('blue','absolute','10','0','15','100','visible');
    makeLayer('image','absolute','50','45','50','40','visible'); 
}
</script>
 
</head>
<body onload="makeUnits(); build();" onResize="history.go(0);" scroll="no">
 
<script language="javascript">
//Finds the windows inner resolution, then sets the new units. Must be in the 
//document body!
function makeUnits() {
    pageHeight = findLiveHeight();
    pageWidth = findLiveWidth();
    xu = pageWidth/100
    yu = pageHeight/100
}
</script>
 
<div id="image"><img src="logo.png" border=0 width=100% height=100%>
</div>
 
<div id="green">
</div>
<div id="blue">
</div>
<div id="red">
</div>
 
 
</body>
</html>

The CSS code:

<style type="text/css">
#red {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:2; 
background-color:red; visibility:hidden;}
#green {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:3; 
background-color:green; visibility:hidden;}
#blue {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:4; 
background-color:blue; visibility:hidden;}
#image {position:absolute; width:0px; height:0px; left:0px; top:0px; z-index:1; 
background-color:none; visibility:hidden;}
</style>

We begin by setting up each layer using the above CSS id (#) style definitions. As you can see from the above code, the page layers are not sized or positioned using CSS, all of these values are set to zero. Later on we will size and position the layers using a JavaScript 'object constructor', which I will explain later. Also note that all of the layers visibility's are set to 'hidden', again we will use JavaScript to turn on the layers once the page is fully loaded.

The Browser Inner Resolution Detection Script:

<script language="javascript">
//detects window inner resolution
function findLiveHeight(){
    if(window.innerHeight != null)
        return window.innerHeight;
    if (document.body.clientHeight != null)
        return document.body.clientHeight;
    return (null);
}
function findLiveWidth(){
    if(window.innerWidth != null)
        return window.innerWidth;
    if (document.body.clientWidth != null)
        return document.body.clientWidth;
    return (null);
}
</script>

The above script, with the functions findLiveHeight() and findLiveWidth(), is placed in the document head. These functions use feature sensing to determine which method that the user's browser in using to find the browser inner resolution. Netscape uses window.innerHeight and window.innerWidth, while Internet Explorer uses document.body.clientHeight and document.body.clientWidth. Now the following script is placed in the document body, calling the two above functions:

<script language="javascript">
//Finds the windows inner resolution, then sets the new units. Must be in the 
//document body!
function makeUnits() {
    pageHeight = findLiveHeight();
    pageWidth = findLiveWidth();
    xu = pageWidth/100
    yu = pageHeight/100
}
</script>

Internet Explorer requires that this script be called from the document body (Netscape 6+ doesn't seam to mind if it is called from the head).

Building the New Units

Let us look again at the above function 'makeUnits()'. After all, this is where all the controlling variables are in this example. Firstly, note the absence of the 'var' statement before all of the variables; this ensures that the variables are accessible by all scripts on the page (i.e. global variables), and not just this script (i.e. local variables). Try running this script with the var statements included, and you will run into script errors.

Now we will make the actual units that will replace the default pixel units for this page, namely 'xu' (horizontal units), and 'yu' (vertical units). We take the browser's 'pageHeight' and divide it by 100 to gain 1% of the browser's inner height value as the new 'yu' units and we do the same for the 'pageWidth' to make the new 'xu' units. This effectively creates a 100x100 grid of the window, which will now become our basis for the positioning and sizing of all screen elements, independent of any change in the actual browser window dimensions. We can now use these units in the object constructor, because the variables 'xu' and 'yu' are global.

The Object Constructor:

<script language="javascript">
//makes dynamic layer
function makeLayer(id, position, left, top, width, height, visibility) {
    this.obj = document.getElementById(id).style;
    this.obj.position = position;
    this.obj.left = parseInt(left*xu);
    this.obj.top = parseInt(top*yu);
    this.obj.width = parseInt(width*xu);
    this.obj.height = parseInt(height*yu);
    this.obj.visibility = visibility;
    return this.obj;
}
</script>

Information is sent to the above 'makeLayer' function in the following way:

makeLayer('red','absolute','0','10','100','20','visible');

As most of the variable names are self explanatory, I will not bore you to death by going over each one of them. The standard W3C 'getElementById' DOM is utilized to grab the relevant layer, and all of the horizontal settings (left and width) are multiplied by the 'xu' units to create the dynamic x-axis values, while the y-axis values are gained in a similar manner.

The 'parseInt' (parse integer) function is used to remove any numbers after the decimal point from all of the size and position values, e.g. 101.1786 becomes 101 pixels, to avoid any possible confusion in your browser interpreting the figures, while speeding up the processing times. Finally, the newly formed layer is switched on by setting it's visibility to 'visible'.

Some Important Loose Ends

The 'body' tag contains the following event handlers to trigger all of the necessary functions:

<body onload="makeUnits(); build();" onResize="history.go(0);" scroll="no"> 

The first thing that happens when the page loads is that the 'makeUnits()' function is called, setting up the 'xu' and 'yu' units. Next, the 'build()' function is called to make the individual layers (it makes sense to place all of these 'makeLayer' calls in the one place). Using the 'onResize' event handler, the page will refresh if the user resizes their browser window, and will therefore resize all of the layers to suit this new window size. This should be clear from the above demo. The statement 'history.go(0)' effectively acts in the same way as your browser's 'refresh' button, it is very useful here but also has many other uses.

Also notice that the image is sized in the 'img src' tag as 100% by 100%, this allows the image to size itself to the exact dimensions of the layer that it resides within, meaning that you do not have to code for images separately in this page.

Concluding Notes

If you have already played around with the above demo, you may notice that the image does not really look too good at certain window sizes (especially small ones), while the layers are often inclined to pull themselves apart, creating white cracks between them. Liquid Design is not without it's drawbacks, and it is for this reason that I would only recommend it for sites that have a simple layout and interface, as the more complicated the site is, the more that can go wrong at very high of very low window resolutions.

For Design-Ireland.net, for example, the complicated interface did not lend itself to the liquid design method. It was just to unpredictable! I worked on developing the Fixed Design method as a more stable alternative, and it is this technique that I will discus in part 3 of this series of articles.

John Collins

I have been writing about web technology and software development since 2001. I am the developer of the Alpha Framework for PHP, and the five.today personal productivity app. I love open source, technology, and economics.