Responsive fluid multi-column layouts – the DIV order problem – II

In the last article of this series

Responsive fluid multi-column layouts - the DIV order problem - I

I discussed a standard 3 column fluid design. HTML and CSS codes for a simple example were presented. Our solution depended on a certain order of DIV tags in the HTML code. The tag of the DIV container for the main content "div#main_content" got the last tag position and the CSS property "position:relative;" inside our outer container "div#float_cont", where the container for 2 of the visible outer left and right "columns" were floated. By setting proper margins for the main content container we gained fluidity and at the same time automatic control over the apparent visual heights of all containers.

In this second article we make a first step towards a responsive design. We assume that the reader is already familiar with the definitions of the major DIV elements of our example page. As in the first article we shall identify the DIVs by their CSS ID representation.

We shall need to overcome some obstacles whilst getting responsive. Because this is a Linux blog we do not care if older and not W3C compliant MS IE browsers can reproduce our suggested CSS recipes for a solution. Code examples given below were tested with Firefox for Linux.

Responsiveness based on viewport width intervals

To start with responsiveness we first define some view port width intervals, for which we want to rearrange major (HTML) elements of our test page. We choose the width intervals a bit arbitrarily below. You may adapt them for your needs:

  • Range III: width range beyond 800px and larger
  • Range II: width range between 540px and 800px
  • Range I: width range below 540px

What are our objectives for the page layout in these ranges?

I shall set up some requirements and conditions for which the DIV order problem gets obvious. What I want to show is that when you deal with responsiveness you may have to align your initial standard HTML/CSS layout with what you want to achieve in view port ranges below certain width thresholds. Some people claim that one should always start designing for the narrow view port situation. My point of view is that with fluid layouts you need to consider both ends of your ranges right from the beginning.

Objectives for Range III:

In range III we just use our (or an improved) initial fluid design.

Objectives for Range II:

In range II the left column shall remain where it is. However, we remove the information in the right column to get more space for the main contents. Instead of omitting "div#right" completely we want to move it to the bottom of the page - and change its width at the same time to 100%. The optical and real height of "div#right" shall be determined by its contents. The "div#main_cont", however, should keep its position, but use all the space being available at the right of the left column to the right edge of the view port. At the same time "div#main_cont" shall determine the optical (not necessarily the css) height of its own and the left column - if it is the longest. Otherwise the contents of the left column shall determine the height of the upper part on the page.

Objectives for Range I:

We set up an even more complicated scenario for range I:
The "div#main_cont" originally positioned in the middle shall become dominant now and take the whole width offered. In addition we want to move the left side menu (div#left_nav") from its original position. To make things tricky we want it to move to the bottom of the expanded DIV container "div#main_cont)" and give it a 100% width, too - so that if someone read and scrolled to the bottom of the "div#main_cont" he would see the available link options. The originally right column "div#right" shall move further down below the repositioned "div#left_nav".

In addition we want to stop a further shrinking of the displayed main DIV container by a minimum width of 200px.

Later on

  • we shall in addition take care of a better treatment of the horizontal menu placed at the top of our test page in range I
  • and offer additional options to deal with the side menu originally placed left with vertical orientation.

Additional assumptions and requirements:

To make things really difficult, we want to achieve the transitions between Range III and Range II as well as between Range II and Range I without changing the HTML code defined for Range III. In addition we want to avoid Javascript/jQuery involvement to change the node order in the HTML tree during transitions. The reordering of the DIV containers defined above shall be achieved by CSS measures only.

Objective of this article - let us cover range II

We take these challenges step by step. For this article our goal is to cover Range II correctly - we do not care about Range I, yet. Already this limited objective will require some fundamental changes of our initial HTML code for Range III - and some interesting CSS tricks. But it will result in a solution which can also be used for Range I later on.

The order of the DIV tags prevents the repositioning of the DIV containers

We shall of course try to add some width dependent statements to our CSS file:

@media only screen and (min-width: 540px) and (max-width: 800px) { ... }

But, what CSS directives could help us to move the container "div#right" to the bottom, i.e. below "div#main_cont" - without loosing our design achievements described in the last article? The frustrating result of many trials and errors was:

Given our assumptions above: none, actually. At least I was too stupid to find a reasonable solution.

You may play around a bit yourself. Even if you introduced some additional (floated) helper DIVs which would only be displayed in range II: Whatever you do to move the "div#right" - some of our requirements would be broken and/or the flow of the text in "div#main_cont" would be affected.

The first ugly thing is the following:
If we floated all major elements of the "div#float_cont" container "left" or "right" the final visible vertical order would not be correct. "div#main_cont" comes at the bottom of the original HTML code - so it moves to the bottom - whatever else happens. The same thing would happen if you turned off floating completely and used only relative positioning. You do not get the final order right.

So, what if we moved "div#main_cont" in our original HTML code and floated it instead of working with margins? Well, you cannot just float "div#main_cont" after (!) "div#left_nav" and keep up the adaptivity to any further width reduction of the viewport. This is mainly due to the fact that our left column container shall keep up a fixed width! You could solve the adaptivity by changing "div#main_cont" to "position:absolute;" (and use "left:16rem; right:0;") but then again you would loose any height control for "div#float_cont" and its external DIV containers. A loss of height control would also happen if you changed "div#info" to "position:absolute;" or if you floated it.

Now, you may think about additional floated helper DIVs which you switched on in the background of "div#main_cont" only after you passed a width threshold. But such a background floated DIV would have an impact onto any text written in "div#main_cont" ! Now, you could solve this by moving the text to an absolutely positioned inner container - and again loose height control. And so on, and so on ...

What do we need?

From all my own experiments I could conclude 2 things which one needs to meet the adaptivity scenarios posed above:

  1. You need the HTML tag of "div#main_cont" as the first one inside the "div#float_cont" tag - more precise: before the other floated DIVs, which define the other columns.
  2. You sooner or later need "div#main_cont" to float itself.

However, as we have already seen at the end of our last article: Just moving the "div#main_cont" tag to the top and keeping up "position:relative;" will not work. However, if we floated it we would loose width adaptivity and/or would not get "div#left_cont" at the correct position with a fixed width. Is the last statement really true?

Negative margins to the rescue

We need to dig a bit deeper in our box of dirty CSS tricks. Let us assume that "div#main_cont" really were the first element of "div#float_cont":

<div id="outer_cont">
	<div id="nav_left_bg"></div>
	<div id="right_bg"></div>
	<div id="info_bg"></div>
	<div id="float_cont">
		<div id="main_cont">
			<div id="info"> ... </div>
		</div>
		<div id="left_nav">
			<div id="left_inner"> ... </div>
		</div> 
		<div id="right">
			<div id="right_inner"> ... </div>
		</div> 
		<p class="floatstop"> </p>
	</div>
</div>

And let us further assume that we really floated "div#main_cont". How could we regain width adaptivity and our text of "div#info" at the right position? Actually, this is pretty easy:

div#main_cont {
	position: relative; 	
	float: left;
	margin: 0; 
	width:100%;  /*adaptivity to the port view width!*/
}
div#info {
	/* height control: the contents defines the height of all parents */ 
	position: relative; 
	margin: 0 27% 0 16rem;
	padding: 0 0.8rem 0 0.8rem;
}

Note the "width:100%;" definition! It guarantees width adaptivity despite floating! To reserve enough pace at the left and right side of the main contents, we just use the same margin trick for "div#info" inside its container which we previously used for "div#main_cont" itself. Nice!

But, how do we get "div#left_nav" to its left position, now? We could use "position:absolute;" for this element. But, by doing this, we could no longer control container heights in case that the left column due to more contents ever became the longest of all columns. However, if we floated "div#left_nav", it would move it downwards because "div#main_cont" already consumes all of the view port's width.

Wait a second: What if we used negative margins? This could lead to a solution, because successive float elements can move across and over each other. So, lets try it:

div#left_nav { 
	position:relative; 
	float:left;
	width: 14.2rem;
	margin-left: -100%;
}
div#right { 
	position:relative; 
	float:left;
	margin-left: -26%;
	width: 26%;
}

Some experiments with your browser would prove that this really works! After corresponding changes to the HTML and CSS codes we find that we more or less have come back to our original fluid scenario - but now the initial order of the HTML tags has changed fundamentally. Everything is floated - but by applying some tricky margins to our central containers we achieved the requested fluid layout again. How can we make use of the new tag order and the floating in range II ?

Elementary settings for range II

As soon as we reach the view port width threshold for range II, only some minor CSS changes are required for our new basic HTML/CSS layout:

@media only screen and (min-width: 540px) and (max-width: 800px) {
	div#right {
        	margin-left: 0;
		margin-top:1.0rem;
		width:100%;
		background-color: #FEEEBB;
	}
	div#info { 
		margin: 0 0 0 16rem;
	}
	div#right_bg {
		visibility: hidden; 
	}
	div#info_bg {
		right:0;
	}
}

This is pretty cool, isn't it? We use our 100%-width of "div#main_cont", change only one "left-margin" statement - and we have moved the "div#right" down. In addition we change the "right-margin" of "div#info" to use the total (adaptive!) width to the right for our main text information. The rest of the CSS changes just repairs some small gaps in the "optical appearance":

fluid4

How to improve the optical appearance in Range III and Range II substantially?

As a developer, I thought I had achieved something and deserved a beer. However, I met strong criticism from my wife, who cares much more for optical design aspects than me. She pretty soon detected a major drawback of my HTML/CSS design:

The repositioned "div_right" still extends the height of the container "div#float_cont". Thus, the heights of our colored background stripes "nav_left_bg", "info_bg" and "right_bg" get bigger, too. The horizontally enlarged and moved "div_right" just overlaps and hides the background stripes.

Note, that we defined a "margin-top:10px;" for the repositioned "div#right;" in the CSS code above. Nevertheless, you see that our background stripes stretch down without any visible gap. As a result, I was confronted with a new requirement:

New additional requirement

What if you wanted to decouple the repositioned "div#right" from the 2 floated DIV containers above throughout Range II - in a clear optical way by a vertical gap - and also in the sense of HTML? And provide a region or stripe of transparency in between - i.e. in the gap opened by the margin-top definition)? Transparency, to allow the user e.g. to look at a background image of the whole page?

This requirement gave me some headache. But I understood the related objective very well. Web pages with seemingly floating areas (with rounded corners) above background images do look fancy, true enough. The eventual solution required additional changes to our initial HTML setup. I had to implement a kind of repetition of our new basic approach in a more convoluted way. Decoupling inevitably leads to more containers ...

Before looking at the code, let us first have a look at the result. As a starting point the new page appearance within Range III:

fluid5

A comparison with the next picture provides an impression of "width fluidity" in Range III:

fluid6

Now, let us have a look at the transition to Range II:

fluid7

And the width fluidity again:

fluid8

Even my wife accepted this - though, of course, she did not like the chosen background colors and the spacing of my test example. But related changes are within the reach of her work domain, now.

The new HTML code

Here are the resulting HTML and CSS codes for our test example. In comparison to our first article the statements may look a bit more complex. But, we only apply already discussed recipes at additional nested tag levels:

New HTML code:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fluid standard 2</title>
<link href="css/fluid_standard2.css" rel="stylesheet">
</head>

<body>
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="hmen_knapp_cont" class="hmen_knapp_cont">
			<a id="hmen_knapp"></a>
		</div>
		<div id="hmen_cont" class="hmen_cont">
			<ul>
				<li><div class="hmen_pkt">h-menu 1</div></li>
				<li><div class="hmen_pkt">h-menu 2</div></li>
				<li><div class="hmen_pkt">h-menu 3</div></li>
				<li><div class="hmen_pkt">h-menu 4</div></li>
				<li><div class="hmen_pkt">h-menu 5</div></li>
				<li><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
				<li class="floatstop"></li>
			</ul>
		</div>
	</div>
	
	<div id="outer_cont">
		<div id="nav_left_bg"></div>
		<div id="right_bg">
			<!-- do not remove ! -->
			<div id="right_inner_bg"></div>
		</div>
		<div id="info_bg"></div>
		
		<div id="float_cont">
			<div id="main_cont">
				<div id="bg_left"></div>
				<div id="bg_info"></div>
				
				<div id="info_cont">
					<div id="float_info">
						<div id="info"> 
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>This is the end, my friend<br> </p>
						</div>
					</div>
					<div id="left_nav">
						<div id="left_inner">
							<p><a href="#">sub menu point 1</a></p>
							<p><a href="#">sub menu point 2</a></p>
							<p><a href="#">sub menu point 3</a></p>
						</div>
					</div>
					<p class="floatstop"> </p>
				</div>
			</div>
			
			<div id="right">
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>
					<p> </p>
					<p>Some stupid text - just to show something</p>
					<p>Some stupid text - just to show something</p>
				</div>
			</div> 
			
			<p class="floatstop"> </p>

		</div>
	</div>
</div>
</body>
</html>

 

You see that I took the decoupling of the left navigation area and the main contents container from the "div#right" container seriously. Actually, we reproduced the very same solution found above - but now inside an additional container "div#info_cont", which itself resides inside the now floated container "div#main_cont". Note, that by applying "position: relative;" for the container "div#info_cont" we can keep up full height control. Of course, in addition the floating inside the new additional float container "div#info_cont" has to be stopped to guarantee the dynamical propagation of height information to the outer enclosing DIVs.

As you can see, we placed some new and additional (colored) background DIVs inside the (now floated) "div#main_cont". But throughout Range III we keep these elements hidden! However, in Range II we hide the original background stripes and activate the new ones instead.

The positioning of the container "div#right" works exactly as before - as log as we arrange for proper margins of "div#info". To get a really nice padding of the background stripes in both ranges and to keep a constant distance of the contents of "div#right" to the contents of "div#info", we need to include some more internal DIV containers - "div#right_inner_bg", "div#left_inner", "div#right_inner". They provide some required "optical cosmetics".

The new CSS code

The required new CSS statements for our example are the following:

   
@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
    margin-left:0; 
    margin-right:0;
    margin-top: 0; 
    background-image: url(../image/hg_drm_ap_7_1.jpg);
    background-repeat:no-repeat; 
    background-position:top center;
}

p { 
	font-size: 1.6rem; 
	line-height: 1.4;
	margin: 0;
}
	

div#all { 
	position:relative; 
	width:100%; 
	padding-bottom: 1.0rem;
	padding-top: 1.0rem;

}

/* The header region */	

div#head { 
	position:relative; 
	width:100%; 
	min-height: 3.0rem;
}

/* The main contents container */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	min-height: 10.0rem;
	margin-top:1.0rem;
}
	

/* some elementary width definitions */

div#left_nav,
div#nav_left_bg,
div#bg_left {
    width: 14rem;
    margin-left: 1.0rem;
}

div#right,
div#right_bg {
    width: 27%;
}


/* background elements for all columns in range I */

div#nav_left_bg, 
div#right_bg,
div#info_bg,
div#bg_left,
div#bg_info,
div#right_inner_bg {
    position: absolute;
    top:0;
    bottom:0;
    border-radius: 0.8rem;
}

div#nav_left_bg {
    position: absolute;
    left:0;
    background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#info_bg {
    position: absolute;
    left:15.8rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}

div#right_bg {
    right: 0;
    border: 0px solid #F00;
    z-index:1;

}

div#right_inner_bg {
    left: 1.0rem;
    right: 1.0rem;
    background-color: #FEEEBB;
}


	
/* The float container and its basic elements */	

div#float_cont { 
    position:relative; 
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:5;
}

/* floated left main container and 
 * its background elements for range II*/

div#main_cont { 
    position: relative;
    float: left; 
    width:100%;
    min-height: 2.0rem;
    border: 0px solid #009900;
    z-index:2;
}


div#bg_left {
    visibility: hidden; 
    position: absolute;
    left:0;
    background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#bg_info {
    visibility: hidden; 
    left:16.0rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}


/* The main column */

div#info_cont {
    position: relative; 
    width:100%; 
    border:0px #F00 solid;
}

div#float_info { 
    position:relative; 
    float:left;
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:2;
}

div#info { 
    position: relative; 
    margin: 0 27% 0 16rem;
    width:auto;
    padding:0.8rem;
    min-height:2.0rem;
    z-index: 1;
}


/* right column */

div#right { 
    position:relative; 
    float:left;
    margin-left: -27%;
    min-height:2.0rem;
    border: 0px solid #009900;
    z-index:2;
}

div#right_inner {
    position:relative;
    width:auto;
    margin-left: 1.0rem;
    margin-right: 1.0rem;
    padding: 0.8rem;
}


/* left column */

div#left_nav { 
    position:relative; 
    float:left;
    border: 0px solid #009900;
    margin-left: -100%;
    padding-left: 1.0rem;
    z-index:5;
}

div#left_inner {
    width:auto;
    padding: 0.8rem;
}


/* Support elements */ 	

p.floatstop {
	clear:both;
	height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}



/* contents of the upper horizontal menu */

div.hmen_cont {
	display: block; 
	position: relative;
	min-height: 3.0rem;
	width: auto;
	margin-right:0.8rem;
	margin-left:0.8rem;
	background-color: #ccc;
	border-radius: 1.0rem;
}

div.hmen_cont ul {
	position: relative;
	list-style-type: none;
	width: 100%;
	margin: 0;
	padding: 0;
}

div.hmen_cont ul li {
	float: left;
	padding: 0.2rem 4.0rem 0.2rem 4.0rem;
	border-right: #a90000 0.2rem solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}
	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	min-height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

div#hmen_knapp_cont {
	display: none;
	position: absolute;
	right:0;
	top:10px;
	width: 50%;
	height: 2.4rem;
	border: 1px #A90000 solid;
}

a#hmen_knapp {
	display: block;
	width: 100%;
	height: 100%;
	background-color: #009999;		
}

a#but2 {
	display: block;
	width: 2.0rem;
	height: 2.0rem;
	background-color: #EEE;
}


/* @media screen decision and settings for range II */

@media screen and (min-width : 540px) and (max-width :800px) {
    
    div#info { 
        margin: 0 1.0rem 0 16rem;
        background-color:transparent;
    
    }
   
    div#right { 
        position:relative; 
        float:left;
        margin-top:1.0rem;
        margin-left: 0;
        margin-right: 0; 
        width:100%;
     }
    
    div#right_inner {
        margin-left: 1.0rem; 
        margin-right: 1.0rem;
        width: auto; 
        background-color: #FEEEBB;
        border-radius: 0.8rem;
    }
    
    
    div#right_bg {
        visibility: hidden; 
    }

    div#info_bg {
        right:0;
    }

    div#nav_left_bg {
        visibility: hidden; 
    }

    div#info_bg {
        visibility: hidden; 
    }
    
    div#bg_left { 
        visibility: visible;
    } 
  
    div#bg_info { 
        visibility: visible;
        right: 1.0rem;
    }
}

 
Of course, you should replace the background image by your own.

Whilst studying the CSS statements, you may find that we extended the width of the right column to 27% of the view port width (instead of 26% before). Thus, physically "div#right" now connects directly to "div#info" at its left side. We did this to build up a seemingly constant (and not percentage dependent) visual distance between the middle and the right container contents. This may sound contradictory at first - but by using the additional inner DIV containers mentioned above and by applying proper margin/padding definitions we do achieve this objective.

At the bottom of the CSS file you find the necessary "@media screen" decisions and settings for range II. Because we set the negative left margin for "div#right" to zero this container moves down. The display of its contents requires some cosmetics; but the related CSS statements are very elementary.

One of the more interesting tricks is that we obviously hide the original (absolutely positioned and colored) background stripe containers and activate the new inner colored background stripe containers instead as soon as we perform the transition to Range II. This allows for a transparent gap between the visible upper part and the lower part of the web page in Range II - without loosing any height control for the visual columns in the upper part.

What have we achieved for our view port Ranges III and II?

Actually, all we wanted. We keep up optical height control where necessary in Range III and II. The CSS height of each of the container DIVs which represent the columns is completely determined by its contents (which we could change at any time, e.g. by a CMS). The longest of the three columns stirs the apparent visual height of all three columns in Range III.

In Range II we detach "div#right" and move it to the bottom of the page. Although we now create a gap between "div#right" and the upper part of the web page, full height control remains in place both for the floating 2 upper DIVs containers and their enclosing containers.

Fluidity is completely fulfilled in accordance with our objectives both for Range III and Range II.

The transition between Range III and Range II requires only some minor and obvious CSS changes. We did not have to change the HTML code at all during transition. We did not involve any Javascript so far.

Enough for today. We have not done anything, yet, to cover the objectives for Range I. I shall describe some appropriate measures in the next article:

Responsive fluid multi-column layouts - the DIV order problem - III

A good question the reader may consider in the meantime is:
What needs to be changed if we wanted to move the third column of Range III upwards instead of downwards during the transition to Range II?