Responsive fluid multi-column layouts – main menu control with JS/jQuery and font-size adaption – V

We continue with our series on responsive and at the same time fluid layouts with multiple columns :

Responsive fluid multi-column layouts – the DIV order problem – I
Responsive fluid multi-column layouts – the DIV order problem – II
Responsive fluid multi-column layouts – the DIV order problem – III
Responsive fluid multi-column layouts – with a responsive horizontal menu – IV

In the first article we had posed the problem of a required DIV order for an efficient realization of responsive fluid layouts. A complete overview over our responsive test scenario and over related HTML/CSS codes was given in articles II and III. In the fourth article of the series we looked at the responsive adaption of an originally horizontal menu to different viewport width ranges. So far all without Javascript [JS].

At small viewport widths (our so called viewport width Range I; see the second article of this series for a definition of viewport width ranges) we offered a “menu button” instead of a complete horizontal menu line. A mouseOver event over the button container (and in smartphones a tap on the menu button) opened the menu – but now in an adapted vertical form. See the pictures included in our last article.

Because no Javascript was used, the user had to move the mouse outside the visible button and menu areas to make the menu disappear. On a smartphone he/she would have to tap the finger to a point outside the visible menu area. Though functional (e.g. for users who deactivated JS) this kind of handling could be confusing. JS/jQuery gives us the chance to improve the situation by reacting to button clicks. We shall implement the required functionality in an object oriented way.

JS in addition gives us the opportunity to load or exchange CSS files. This may help us to react to the detection of mobile devices and e.g. adjust font-sizes by loading an adapted CSS file.

We shall discuss and apply both JS based techniques to our simple responsive test example. So, be prepared for a long code intensive article and switch the coffee machine on. Those who are impatient and rather would study code first may jump to this paragraph.

New objective for user interaction with the menu button

A more consistent handling of successive clicks on our “menu button” and of resize events after clicks would be:

  • A first click on the menu button in Range I should make the menu visible (in vertical form and above all other page elements).
  • A subsequent second click should hide the menu again.
  • A “mouseover” event should become obsolete.
  • When we resize the browser menu on a desktop by mouse dragging or on a smartphone by device rotation the menu should return to the original form and visibility defined by the CSS settings for the viewport width range reached.

In other words: The menu button in Range I should work like a toggle button for successive clicks and viewport resizing shall give the standard CSS settings priority again.

Before reading further about a series of details you may want to try our simple test example:
https://linux-blog.anracom.com/examples/fluid/fluid_standard2.html

Toolset and JS strategy

We will use jQuery. Our JS strategy will be to create a “control object” [CtrlO] based on a constructor with prototype functions (“methods”) to manage the event handling. The approach of delegating event handling to object methods may appear unusual to JS beginners – but the initially required code overhead (in comparison to elementary “onClick”-handlers for the HTML elements) eventually leads in my opinion to a much better structured and manageable code for more complex pages – one CtrlO for each distinguished UI interaction area. This blows up the JS discussion a bit – but may be an interesting side aspect for some readers.

We assume that the reader is already familiar with the HTML code for the menu and the related CSS definitions presented in the last article. Before starting a in depth JS code discussion we first discuss some typical obstacles to overcome.

General challenges of a Javascript involvement for responsiveness

There are two major challenges that arise when you want to realize responsiveness mainly via Javascript and not only via CSS width conditions of the form

@media screen and (max-width : ….) { CSS statements for defined viewport width ranges }

To understand the type of problems that may occur imagine a user who continuously changes the viewport width up and down – e.g. by changing the browsers window size with the mouse on a desktop or indirectly by using the browsers scaling functionality. On a smartphone he/she could rotate the device from vertical to horizontal orientation and back again. Both actions trigger frequent or even permanent window “resize”-events to which the browser must react properly.

Problem 1: Priority of CSS property changes done by JS

One important thing is that CSS properties changed by Javascript – e.g. during the transition over viewport width threshold – automatically get a high priority:

Whatever CSS properties of HTML elements you set explicitly with JS: The change works as if a the property was changed inline for the affected DOM tree element (or as if a new line were added to the bottom of your CSS code). This in turn means: All previous settings – including the ones for the viewport width ranges – are overwritten! As a consequence your responsiveness may be hampered. E.g., if you at a viewport width threshold changed the position property of an HTML element to “absolute” via Javascript it would without any further measures stay so afterwards – even if your viewport width changes and your CSS rules for a certain viewport width range said “position:relative;”.

You could avoid this by using the CSS “!important” flag for the affected CSS properties in your CSS rules for certain viewport width ranges. But this “protection” of the property setting may in realistic scenarios conflict with other temporal and reasonable changes inflicted by other user UI interaction in a certain viewport range.

Alternatively you could check all the time by JS whether some new resize events are triggered in your browser and compare the present viewport size to threshold values and reset properties by Javascript again. This leads to problem 2. Actually, for most practical cases there is a more intelligent way to deal with this problem. See below.

Problem 2: Permanent tracking of resize events and aligning of CSS property settings between JS and our basic CSS definitions

If you need to make property changes via JS in certain viewport ranges, JS must get information about the present size of the viewport and must restore desired CSS property values. Your JS could react to the “window.onResize” event – which fires permanently as long as the browser window size changes. Ok, but if you have to
follow this event permanently and make all the same decisions all the time then JS will consume CPU time. And depending on the kind and depth of decisions this can have a significant effect on performance. On the other side you MUST align the decisions made on the Javascript side with your standard CSS settings for viewport width ranges defined in your CSS files, or inline CSS definitions for tags in the HTML code (i.e. in your CSS file). Together with problem 1 this may become a logical challenge.

Some rules to overcome possible obstacles of JS usage in responsive scenarios

How to circumvent the obstacles discussed above? In my opinion rule number 1 is:

You should always build your responsiveness mainly on pure CSS definitions for width ranges and involve JS only where and when absolutely necessary or helpful. The advantages in my opinion are: A CSS centered approach is more efficient, definitions for responsiveness are concentrated at one point and directly connected to other layout definitions, most of the maintenance does not require any programming knowledge and the performance of the browser gets much better.

In our specific test case: Involve JS only for the handling of the users interaction with the menu button in Range I and restrict the use of JS for this button – as it is only visible in viewport width Range I.

If you follow this main rule then the combination of two simple additional “tricks” can be helpful:

  • Make your JS functionality intelligent enough to react to resize events only when necessary and restore the old conditions as soon as a resize happens. But do it only once (!) to avoid a permanent tracking of the resize event and a permanent repetition of decisions and reactions to it.
  • Change CSS property values via loading an additional CSS class into a HTML object. Restoration of the original properties then becomes as simple as just removing the class again from the HTML object.

The first point means that we actually may sometimes track a resize event and react to it – but only under certain conditions. This is possible via dynamically switching the event handling On and Off again. More specifically: The very function that was triggered by an event handler for the resize event may turn off the same event handler!

We shall show below how to realize these rules for our test example. The basic principles can be transferred to more complex tasks.

Removing the existing CSS “hover” pseudo class definitions with jQuery

There is another small obstacle which we need to overcome in our test example. For width Range I we had defined a rollover effect via a “:hover” definition for one of the inner menu containers “div#menu_cont”:

Excerpt of the HTML code for the horizontal main menu

<body onload="init_page();">
<div id="all">
	<div id="head">
		<div id="main_menu_cont">
			<div id="bg_hor_menu"></div>
			<div id="menu_cont" class="hover">
				<a id="hmen_but" title="main menu" href="#"></a>
				<div id="hmen_cont" class="hmen_cont">
					<div id="bg_ver_menu"></div>
					<ul id="menu_lst">
						<li><div class="hmen_pkt"><p><a href="#">h-menu 1</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 2</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 3</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 4</a></p></div></li>
						<li>&
lt;div class="hmen_pkt"><p><a href="#">h-menu 5</a></p></div></li>
						<li class="hm_li_right"><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
						<li class="floatstop"></li>
					</ul>
				</div>
			</div>
			<p class="floatstop"> </p>
		</div>
	</div>
....
....
</div>
....
....

 

@media screen and (max-width : 539px)  {
....	
	div#menu_cont:hover div.hmen_cont {
		visibility: visible; 
	}
....
}

 
This provided us with a working solution for browsers with deactivated JS. However, when the target browser allows for JS we do not want this CSS pseudoclass definition to be active, because the “hover” effect shall be replaced by clicks on our menu button.

Unfortunately there is no way to change pseudo class definitions directly with JS or jQuery. Therefore, we move our “hover” settings into a class “hover” which we already smuggled into the HTML code of “div#menu_cont” above.

@media screen and (max-width : 539px)  {
....	
	/*
	div#menu_cont:hover div.hmen_cont {
		visibility: visible; 
	}
	*/ 
	
	div.hover:hover div.hmen_cont {
		visibility: visible; 
	}

....
}

 
What is the advantage? With the help of JS and jQuery we can easily remove CSS classes from a tag. So, if JS is activated in the target browser one of our first actions would be to remove the class “hover” from the “div#menu_cont” element – and we would get rid of the hover effect completely. Thus, we do not need to remove our hover statement from the CSS code and remain prepared for non-JS situations.

A word about font sizes for mobiles

Although it is convenient to use a responsive design as a first step to adapt web pages to smartphones and other mobile devices there is much truth in the statement that responsiveness is not equal to a dedicated mobile support.

First the general user interaction with an interface of limited physical dimensions is different from working on a desktop. Related aspects can be taken care of in responsive HTML/CSS/JS-definitions – but it includes logically more consideration than just planning reactions to small viewport widths in terms of pixels.

Another problem is the fact that due to the physical pixel density on mobiles a responsive layout without adjusting font-sizes may not be helpful. Especially for elderly people who have difficulties with reading small letters. Even if we do not want to investigate and distinguish pixel densities of different smartphones in detail, we would in general like to use bigger font-sizes on a smartphone than on a desktop browser:

Bigger font-sizes in certain viewport width ranges?
One first approach could be to enlarge font-sizes for small viewport width ranges. The disadvantage of this approach is that it also leads to enlarged fonts in desktop browsers, too, when the user shrinks the browser’s window width. But especially on a desktop we do not need to enlarge the normal font-size for narrow browser windows. There it is even counterproductive! So, if you follow a policy to raise font-size values for small viewport widths, do it moderately and without exaggeration.

Check for mobile browsers and load adapted CSS files with larger fonts!
With Javascript I personally would follow a different approach:

  • Firstly, I would use JS to check for a mobile browser and
  • secondly, I would enlarge text(!)-fonts if a mobile browser is detected –
    without changing dimensions of other HTML objects.

We can achieve font-size changes simply by

  • either appending an additional style sheet
  • or replacing the existing style sheet with another style sheet

that contains new font-size definitions and another adjustments for mobiles for all width-ranges. Whether you replace or append might be a question of complexity. If you intend to change important viewport width thresholds I would choose a replacement strategy. When appending additional statements it may become very difficult to see and plan what has to be changed. For our simple test case will will append an additional file. (Its easier to see what we have changed, then.)

What do we need to take care of when changing font-sizes?
Personally, I scale almost all dimensions of my HTML objects by “em” or “rem”. In such a case we would NOT change the overall font-size definition in the <html>- or <body>-tag because this would rescale the whole page layout. We only change the font-settings and adjust other special size related properties for <p> and other tags in the additional CSS file! Note that changed font-sizes may in particular require an adjustment of (min-)heights, line-heights, margins and paddings, also of surrounding elements in our basic layout.

Another topic is the setting of width thresholds for menu adaptions: We saw in the last article that we set our width thresholds for changing padding/margin-values of menu elements such that we avoided wrapping into several menu lines. However, as we have explained, these threshold values depend on the font-size, too. Therefore, we need to adapt or introduce new viewport width thresholds for our menu behavior.

The modified HTML and CSS file

For reasons of completeness we list both the modified HTML and its CSS file below.

HTML file for our test case

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>fluid standard 2</title>
<link id="myMainCSS" href="css/fluid_standard2.css" rel="stylesheet">
<!-- The following CSS sheet will be loaded by JS  -->
<!--<link href="css/fluid_standard2_mobile.css" rel="stylesheet">-->

<script type="text/javascript" src="scripts/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="scripts/detectmobilebrowser_jQuery.js"></script>
<script type="text/javascript" src="scripts/response2.js"></script>
</head>

<body onload="page_init();">
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="main_menu_cont">
			<div id="bg_hor_menu"></div>
			<div id="menu_cont" class="hover">
				<a id="hmen_but" title="main menu" href="#"></a>
				<div id="hmen_cont" class="hmen_cont">
					<div id="bg_menu"></div>
					<ul id="menu_lst">
						<li><div class="hmen_pkt"><p><a href="#">h-menu 1</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 2</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 3</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 4</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 5</a></p></div></li>
						<li class="hm_li_
right"><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
						<li class="floatstop"></li>
					</ul>
				</div>
			</div>
			<p class="floatstop"> </p>
		</div>
	</div>
	
	
	
	<div id="outer_cont">
		<div id="bg_left0"></div>
		<div id="bg_right0">
			<!-- do not remove ! -->
			<div id="bg_right_inner0"></div>
		</div>
		<div id="bg_info0"></div>
		
		<div id="float_cont">
			<div id="main_cont">
				<div id="bg_left1"></div>
				<div id="bg_info1"></div>
				
				<div id="info_cont">
					<div id="float_info">
						<div id="bg_info2"></div>
						<div id="info"> 
							<p>Lorem ipsum ... </p>
							<p> </p>
							<p>Lorem ipsum ...</p>
							<p> </p>
							<p>This is the end, my friend<br> </p>
						</div>
					</div>
					<div id="left_nav">
						<div id="bg_left2"></div>
						<div id="left_inner">
							<ul>
								<li><p><a href="#">sub menu point 1</a></p></li>
								<li><p><a href="#">sub menu point 2</a></p></li>
								<li><p><a href="#">sub menu point 3</a></p></li>
							</ul>
						</div>
					</div>
					<p class="floatstop"> </p>
				</div>
			</div>
			
			<div id="right">
				<div id="bg_right2"></div>
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>telekom störung
					<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 we now load JS files – first the jQuery file of a present version, then a file suited for jQuery which helps to detect browsers. You can download the latter open source file, which contains a long regex test on browser information, from the following web page:
http://detectmobilebrowsers.com/
The last JS file is our own Javascript code (see below).

CSS file for our test case

@CHARSET "UTF-8";

html {
	font-size:10px;
}

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

p { 
	font-size: 1.4rem; 
	line-height: 1.6rem;
	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;
	z-index: 6; 
}

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

/* some elementary width definitions */
/* --------------------------------  */
div#left_nav, 
div#bg_left0, 
div#bg_left1  {
	width: 14rem;
}

div#left_nav {
	margin-left: 1.0rem;
	
}

div#right,
div#bg_
right0 {
	width: 27%;
}


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

div#bg_left0, 
div#bg_left1,
div#bg_left2,
div#bg_right0,
div#bg_right_inner0,
div#bg_right2,
div#bg_info0,
div#bg_info1,
div#bg_info2  {
	position: absolute;
	top:0;
	bottom:0;
	border-radius: 0.8rem;
}

div#bg_left0 {
	left:1.0rem;
	background-color: #FFF;
	opacity: 0.5;
	border: 0px solid #F00;
	z-index:1;
}

div#bg_info0 {
	position: absolute;
	left:16.0rem;
	right:27%; 
	background-color: #FFF;
	opacity: 0.85;
	border: 0px solid #00F;
	z-index:1;
}

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

div#bg_right_inner0 {
	left: 1.0rem;
	right: 1.0rem;
	background-color: #FEEEBB;
	opacity:0.80;
}



	
/* 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:1;
}


div#bg_left1 {
	display: none;     
	left:1.0rem;
	background-color: #FFF;
	opacity: 0.75;
	border: 0px solid #F00;
	z-index:1;
}

div#bg_info1 {
	display: none; 
	left:16.0rem;
	right:1.0rem; 
	background-color: #FFF;
	opacity: 0.9;
	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%; 
	/*background-color: #99e;*/ 
	border: 0px solid #FF0000;
	z-index:2;
}


div#info { 
	position: relative; 
	margin: 0 27% 0 16rem;
	width:auto;
	/*background-color: #0f0;*/ 
	padding:0.8rem;
	/*height:200px;*/
	z-index: 1;
}

div#bg_info2 {
	display: none; 
	left:1.0rem;
	right:1.0rem; 
	background-color: #FFB;
	background-color: #FFF;
	opacity: 0.95;
	border: 0px solid #00F;
	z-index:1;
}




/* 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 {
	position: relative; 
	width:auto;
	padding: 0.8rem;
	z-index: 2; 
}

div#bg_left2 {
	display: none;     
	left:1.0rem;
	right:1.0rem; 
	border: 0px solid #F00;
	background-color: #FFF;
	opacity: 0.9;
	z-index:1; 
}



/* right column */
/* ------------ */

div#right { 
	float:left;
	position:relative; 
	margin-left: -27%;
	min-height: 2.0rem;
	/*min-width: 15.2rem;*/ 
	/*background-color: #eee;*/ 
	border: 0px solid #009900;
	z-index:2;
}

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

div#bg_right2 {
	display: none;     
	left:1.0rem;
	right:1.0rem; 
	border: 0px solid #F00;
	background-color: #FEEEBB;
	opacity: 0.85;
	z-index:1; 
}



/* 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#main_menu_cont {
	display: block; 
	position: relative;
	width: auto;   
	margin-left: 1.0rem; 
	margin-right: 1.0rem; 
	min-height: 3.0rem;
}

div#bg_hor_menu {
	display:block; 
	position: absolute; 
	top: 0;
	bottom: 0;
	left: 0; 
	right: 0; 
	
border-radius: 0.8rem;
	background-color: #FFF; 
	opacity: 0.75; 
	z-index: 1; 
}


div#menu_cont {
	display: block; 
	position: relative;
	float:right; 
	width: 100%;  
	min-height: 3.0rem;
	z-index: 2; 
	
	/* helps to center its enclosed container */
	text-align: center; 
}



a#hmen_but {
	display: none; 
	width: 4.6rem;
	height: 2.2rem;
	margin-top:0.2rem; 
	background-color: #A90000;
	border-top: 0.2rem #CCC solid; 
	border-right: 0.2rem #CCC solid; 
	border-left: 0.2rem #AAA solid; 
	border-bottom: 0.2rem #AAA solid; 
}


div.hmen_cont {
	position: relative;
	
	/* display: block; */  
	
	/* makes horizontal centering possible despite undefined width */
	display: inline-block; 
	
	min-height: 3.0rem;
	
	/* A second method of centering - optically determined by the viewport width */
	/*
	width: auto;
	margin-right:0.8em;
	margin-left:0.8em;
	*/
	
	border-radius: 1.0rem;
	
	/* centers the enclosed list ! */
	text-align: center; 
}

div#hmen_cont.vis {
	visibility:visible;  
} 



div#bg_menu {
	display:block; 
	position: absolute; 
	top: 0;
	bottom: 0;
	left: 0; 
	right: 0; 
	border-radius: 0.8rem;
	background-color: #FFF; 
	opacity: 1.0; 
	z-index: 1; 
}


div.hmen_cont ul {
	position: relative;
	
	/* makes horizontal centering possible despite undefined width */
	display: inline-block;
	list-style-type: none;
	/*width: 100%;*/
	margin: 0;
	padding: 0;
	padding-left: 0.4rem;
	padding-right: 0.4rem;
	z-index:2;
}

div.hmen_cont ul li, 
div.hmen_cont ul li.hm_li_left,
div.hmen_cont ul li.hm_li_right {
	float: left;
	padding: 0.2rem 4.0rem 0.2rem 4.0rem;
	margin:0; 
	margin-top:0.2rem;
	height: 2.2rem;
	/*border-left: #ffffff 1px solid;*/
	border-right: #a90000 0.2rem solid;
	min-height: 2.0rem;
}

div.hmen_cont ul li.hm_li_left {
	border-left: #a90000 0.2rem solid;
}	
div.hmen_cont ul li.hm_li_right {
	border-right: 0; 
}	

	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	height:0; 
	min-height: 0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
	border:0;
}

div.hmen_cont ul li a, 
div.hmen_cont ul li a:visited  {
	text-decoration: none; 
	color: #000; 
}

div.hmen_cont ul li a:hover {
	color: #A90000; 
	background-color: #FFFFBB; 
}



a#but2 {
	display: block;
	width: 1.6rem;
	height: 1.6rem;
	background-color: #A90000;
	border-top: 0.2em #CCC solid; 
	border-right: 0.2rem #CCC solid; 
	border-left: 0.2rem #AAA solid; 
	border-bottom: 0.2em #AAA solid; 
}



/* contents of the left vertical menu */

#left_inner ul {
	width:100%;
	min-height: 2.2rem;
	list-style-type: none;
	padding: 0;
	margin: 0;
}

#left_inner ul li {
	width:100%; 
	min-height: 2.2rem;
}


#left_inner ul li a, 
#left_inner ul li a:visited {
	color: #990000;
	
}
#left_inner ul li a:hover {
	color: #000;     
	background-color: #FFFFBB; 
}
  
    

/* ---------------------------------------------- */    
	
/* @media screen decision and settings for the horizontal menu */
	
@media screen and (max-width : 860px) {
	
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right
	 {
		padding: 0.2rem 1.2rem 0.2rem 1.2rem;
	}
} 


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

@media screen and (min-width : 540px) and (max-width :828px) {
    
	div#info { 
		margin: 0 2.0rem 0 16rem;
		background-color:transparent;
	}
   
	div#right { 
		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;
	}
    
	/*
	 * Switch OFF backgrounds after 1st viewport width transition 
	 */
	div#bg_left0 {
		display: none;  
	}
	div#bg_info0 {
		display:none;  
	}
	div#bg_right0 {
		display: none; 
	}
    
    
	 /*
	  * Switch ON backgrounds after first viewport width transition 
	 */
	 
	 div#bg_left1 { 
		display: block; 
	 } 
	   
	 div#bg_info1 { 
		display: block; 
	 }
	 div#bg_right2 { 
		display: block; 
		opacity: 0.9;
	 }

}

    
/* ---------------------------------------------- */    
  

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


@media screen and (max-width : 539px)  {
    
	
	div#info { 
		margin: 0 1.0rem 0 1.0rem;
		/* background-color: #FFB; */
		border-radius: 0.8rem;
	}
	
	
	div#left_nav { 
		margin-top:1.0rem;
		margin-left: 0;
		width:100%;
		padding:0;
	}
	  
	div#left_inner {
		width:auto;
		padding: 0.8rem;
		margin: 0 1.0rem 0 1.0rem;
		/*  background-color: #DDDDDD; */
		border-radius: 0.8rem;
		z-index: 2; 
	}
	
	div#right { 
		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;
	}
   
	/*
	 * Switch OFF backgrounds after 2nd viewport width transition 
	 */
	
	div#bg_info0 { 
		display: none; 
	 }
	div#bg_info1 { 
		display: none; 
	 }
	 
	div#bg_right0 {
		display: none;  
	}
	 
	div#bg_left0 {
		display: none; 
	}
	div#bg_left1 { 
		display: none; 
	}   
	
	/*
	 * Switch ON backgrounds after 2nd viewport width transition 
	*/
	div#bg_info2 { 
		display: block; 
		opacity: 0.95; 
	}
	div#bg_left2 { 
		display: block; 
		opacity: 0.95; 
	}   
	div#bg_right2 { 
		display: block; 
		opacity:0.9; 
	}


	/*
	* Treatment of the horizontal menu 
	*/
	
	div#bg_hor_menu {
		opacity: 1.0;
	}
	

	div#menu_cont {
		margin-right: 4.0rem; 
		width: 5.0rem;  /* button size ! */
		height: 3.0rem;
	}
	
	a#hmen_but {
		display: block; 
	}


	/*
	div#menu_cont:hover div.hmen_cont {
		visibility: visible; 
	}
	*/ 
	
	div.hover:hover div.hmen_cont {
		visibility: visible; 
	}

	
	div.hmen_cont {
		position: absolute;
		visibility:hidden; 
		top:3.0rem;
		right:0; 
		min-height: 3.0rem;
		
		width: auto;
		min-width: 18.4rem;
		max-width: 25.0rem;
		
		margin-right:0rem;
		margin-left:auto;
		
		padding: 0.8rem; 
		
		background-color: #DDD;
		border-radius: 1.0em;
		
		text-align: left; 
	}
	
	div#bg_menu {
		background-color: #DDD;
		opacity: 1.0;   
	}
	
	
	div.hmen_cont ul {
		position: relative;
		list-style-type: none;
		width: auto;
		margin: 0;
		padding: 0;
	}
	
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right {
		float: none;
		padding: 0.2rem 0.4rem 0.2rem 0.8rem;
		width:auto;
		/*border-left: #ffffff 1px solid;*/
		border-right: #a90000 0.0rem solid;
		min-height: 2.0rem;
	}
		
	div.hmen_cont ul li.floatstop {
		float:none;
	}

	div.hmen_cont ul li p {
		margin-top:0; 
		line-height: 1.6rem; 
	
	}

}    

/* extreme case - reached e.g. by Ctrl + */
/* -------------------------------------- */
@media screen and (max-width : 260px)  {
	div#menu_cont {
		margin-right: 4.0rem; 
	}
	
	div.hmen_cont {Additional CSS file to be loaded dynamically for mobiles
		right: -4.0rem;
		width: auto; 
		min-width: 0; 
	}
	
	
div#bg_menu {
		background-color: #DDD;  
	}
	
}	

 
More or less the same as presented in our last article. Note that we have introduced class “.vis” :

div#hmen_cont.vis {
    visibility:visible;
}

We shall use use this class in our Javascript code.

Additional CSS file to be loaded dynamically for mobiles

@CHARSET "UTF-8";
p { 
	font-size: 1.6rem; 
	line-height: 2.0rem;
	margin: 0;
}

	
/* contents of the upper horizontal menu */
/*-------------------------------------- */

div.hmen_cont ul li, 
div.hmen_cont ul li.hm_li_left,
div.hmen_cont ul li.hm_li_right {
	padding: 0.2rem 3.4rem 0.2rem 3.4rem;
	height: 2.4rem;
	min-height: 2.2rem;
	line-height: 2.0rem;
}

/*-------------------------------------- */
	
/* @media screen decision and settings for the horizontal menu */
	
@media screen and (max-width : 860px) {
	
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right {
		padding: 0.2rem 1.0rem 0.2rem 1.0rem;
	}
} 
    
/* ---------------------------------------------- */    

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

@media screen and (max-width : 539px)  {
    
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right {
		padding: 0.2rem 0.4rem 0.2rem 0.8rem;
	}

	div.hmen_cont ul li p {
		margin-top:0; 
		line-height: 2.0rem; 
	}
}    

 
You see that we raised the <p>-font-size from 1.4rem to 1.6rem and adjusted other properties. This change is moderate – the reason being that I was too lazy to adapt the columns of the whole web structure. For larger font-sizes you had e.g. to enlarge the right menu block. But the font-size effect will be visible – and this is enough for demonstration purposes.

The JS code

The JS code is propbably a bit more complicated than the reader expects – but we shall address interesting points in a minute. Still the code at some points is kept relatively simple and can/must of course be improved for real world cases.

JS code to load an additional style sheet for mobiles and to control the menu button actions at small viewport widths in our test example

* JS example to control the menu in a responsive layout
 */


// --------------------------------------------------------
// Constructor declarations
// --------------------------------------------------------

/**
 * Constructor of a global object controller object 
 * - to prevent clattering of the global space
 * - to build all CtrlOs (each CtrlO is a singleton) 
 */
function Anracon_GlobalObjectController(globalContext) {
	
	this.global = globalContext;

	// Prefixes for CtrlOs and their constructors
		this.ctrloPrefix  = "CtrlO_"; 
		this.constrPrefix = "Ctrl_";  

	// CtrlO names - here we have only one CtrlO, 
	// more CtrlOs would be handled on more complex pages
		this.ay_objNames 	 = new Array('Hmenu');   
		this.len_ay_objNames = this.ay_objNames.length; 


	// address of aditional mobile ccs file 
		this.cssMobileFileUrl = "css/fluid_standard2_mobile.css"; 
	
		
    // Initial action to check for Mobile browser and to load additional CSS sheet 
	// or to replace aaditional CSS sheet 	
	// check for mobile browser and react  
	// --------------------------------	
		
		// Shall the CSS file be replaced ? 
		this.replaceCss = false; // false: CSS file will be appended, true: ex. css file will be replaced
		
		// Tag of 
CSS file to be replaced 
		this.cssLinkId = "#myMainCSS"; 
		
		this.mobile = false; 
		if ($.browser != undefined ) {
			if ( $.browser.mobile != undefined ) {
				this.mobile = $.browser.mobile; 
			}	
		}
		
		// Load additional style sheet or replace existing style sheet to enlarge fonts  
		// done already here to avoid unnecessary flickering 
		this.mobile = true; 
		if ( this.mobile ) {
			this.loadCssFile( this.cssMobileFileUrl, this.replaceCss, this.cssLinkId ); 
		}
}


/**
 * GOC method to create all CtrlOs after (!) web page creation 
 * HTML object must exist before instanciating the CtrlOs 
 */
Anracon_GlobalObjectController.prototype.populate = function() {
	
	var ctrloName; 
	var constrName; 
	
	// Loop over all defined CtrlO names
	// ----------------------------------
	for (i=0; i < this.len_ay_objNames; i++) {
		ctrloName  = this.ctrloPrefix  + this.ay_objNames[i];
		constrName = this.constrPrefix + this.ay_objNames[i];
		console.log("constructor_name = " + constrName); 
		
		// Creation of all required CtrlOs 
		// -------------------------------
		// via indirect addressing and indirect call of constructors 
		this[ctrloName] = new this.global[constrName](ctrloName);  
	}
	
	this.pushObjectReferences(); 
};


/**
 * GOC method to dispatch references to every CtrlO across all CtrlOs 
 * each CtrlO should be able to address other CtrlOs 
 *  
 */
Anracon_GlobalObjectController.prototype.pushObjectReferences = function() {
	
	var i=0, j=0; 
	var presentCtrloName = '';
	var globalCtrloName = '';
	var localCtrloName  = ''; 
	
	var localCtrloPrefix = this.ctrloPrefix;  // We use the same name as CtrlOs are singletons
	
	// Loop over all CtrlOs
	// --------------------
	for (i=0; i < this.len_ay_objNames; i++) {
		
		// "Present" CtrlO to which references of OTHER CtrlOs are dispatched  
		presentCtrlOName = this.ctrloPrefix + this.ay_objNames[i];
		console.log("GOC push references into the following CtrlO :: i = " + i + " global CtrlO name = " + presentCtrlOName); 
		
		// Dispatch all other CtrlO references to the present CtrlO 
		// --------------------------------------------------------
		for (j=0; j < this.len_ay_objNames; j++) {
			if (j != i) {
				localCtrloName	= localCtrloPrefix + this.ay_objNames[j];
				globalCtrloName	= this.ctrloPrefix + this.ay_objNames[j];
				
				if ( this[presentCtrlOName][localCtrloName] == null || this[presentCtrlOName][localCtrloName] == 'undefined' ) {
					
					// assign the reference of the other CtrlO to a variable in the present CtrlO
					this[presentCtrlOName][localCtrloName] = this[globalCtrloName]; 
				}
				
				console.log(" reference to " + globalCtrloName + " assigned"); 
			}
		}
	}
}; 


/**
 * GOC method to load additional CSS sheets
 * 
 * @param url  - address of CSS sheet relative ot the HTML file 
 */
Anracon_GlobalObjectController.prototype.loadCssFile = function(url, replaceCss, cssLinkId) {
	
	var linkIdentifier = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + url + "\">"; 
	var cssLinkToFile = $(linkIdentifier);  // if already there it will not be cloned (see jQuery .append docu) 
	
	var styleSheetSelector = ''; 
	
	if ( replaceCss == undefined ) {
		replaceCss = false; 
	}
	
	if( cssLinkId != undefined ) {
		styleSheetSelector = cssLinkId + "[rel=stylesheet]"; 
	}

	// Non consistent parameters 	
	if ( replaceCss && styleSheetSelector == '') {
		// Include some error handling here .....
		// we just do nothing and thus fall back to the old CSS file 
		return; 
	}
	
	if ( replaceCss == true ) {
		$(styleSheetSelector).attr('href', url);
	}
	
	if ( !replaceCss ) {
		$("head").append(cssLinkToFile);
	}
}; 

r

/**
 * Constructor for the CtrlO which controls 
 * the user interaction with the horizontal menu
 * 
 *  @param obj_name  my own object name in the GOC
 */
function Ctrl_Hmenu(objName) {
	
	// Reference to our GOC
	// ----------------------
		this.GOC = Anracon_GOC;
		this.myName = this.GOC.ctrloPrefix + objName; 
		// other CtrlOs references are received through a dispatching process  	
	
	// HTML IDs = identifiers for jQuery 
	// -----------------------------------	
		this.id_menuCont  = "#" + "menu_cont";
		this.id_hmenuBut  = "#" + "hmen_but";
		this.id_hmenuCont = "#" + "hmen_cont";
		
		this.id_rightInner = "#" + "right_inner"; 
		
	// variables 
	// ----------	
		// button clicked ?   
		this.butMenuClicked = 0;
		// mobile browser ? 
		this.mobile = false; 
		
		
	// Some initial action 
	/// ******************	
		
	// switch off event handler for rollover set by CSS for non JS situations  
	// ------------------------------------
		$(this.id_menuCont).removeClass("hover"); 
			
	// check for mobile browser and react  
	// --------------------------------	
		if ($.browser != undefined ) {
			if ( $.browser.mobile != undefined ) {
				this.mobile = $.browser.mobile; 
			}
		}
		// Add message to the right column for test scenario
		this.rightTxt  = $(this.id_rightInner).html(); 
		this.mobileStr = ''; 
		if ( this.mobile ) {
			this.mobileStr = "<p style=\"color:red; font-weight:bold; margin-top:1.0rem;\">JS: Mobile browser detected!<br>Using bigger font-sizes!</p>" 
		}
		else {
			this.mobileStr = "<p style=\"color:red; font-weight:bold; margin-top:1.0rem;\">JS: No mobile browser detected!</p>" 
		}
		
		// Message 
		this.rightTxt += this.mobileStr; 
		$(this.id_rightInner).html(this.rightTxt); 
		
		// Load additional CSS-sheet
		// --- Not done here, but initially - see GOC ----
		
		
	// register functionality for controlled HTML elements
	// -------------------------
		this.register_events();
		
	console.log("Constructor of Ctrl-Object " + this.myName); 	

}


/**
 * CtrlO method to register events for the active elements of the menu container 
 */
Ctrl_Hmenu.prototype.register_events = function() {

	$(this.id_hmenuBut).click(
		$.proxy(this, 'displayHmenu')
	); 
    
	console.log("From CtrlO Hmenu: Callback for click on menu button registered " );                
};


/**
 * Ctrlo method to display the menu after a click on the menu button 
 */
Ctrl_Hmenu.prototype.displayHmenu = function(e) {
	
	var method = 0;	// 0: load and unload additiinal css class 
					// 1: directly change CSS property visibility
	
	e.preventDefault();
    
	if ( method == 0 ) {
		if ( this.butMenuClicked == 0 ) {
			$(this.id_hmenuCont).addClass("vis"); 
   	   	   	
			// activate handler for window.resize event 
			$(window).on("resize", $.proxy(this, 'removeClassVis') );
   	   	   	
			this.butMenuClicked = 1; 
			console.log("menu button clicked - menu activated" );                
		}
		else {
			// $(this.id_hmenu_cont).css('display', 'none'); 
			$(this.id_hmenuCont).removeClass("vis"); 
			this.butMenuClicked = 0; 
			console.log("menu button clicked - menu deactivated" );                
		}
	}
	else {
		if ( this.butMenuClicked == 0 ) {
			$(this.id_hmenuCont).css('visibility', 'visible'); 
			this.butMenuClicked = 1; 
			console.log("menu button clicked - menu activated" );                
		}
		else {
			$(this.id_hmenuCont).css('visibility', 'hidden'); 
			this.butMenuClicked = 0; 
			console.log("menu button clicked - menu deactivated" );                
		}
	}
};


/**
 * Ctrlo method to remove the visibility class from the menu button 
r
 *  - Note: this helps to avoid trouble with resizing 
 */
Ctrl_Hmenu.prototype.removeClassVis = function(e) {
	console.log("resize event");
	$(this.id_hmenuCont).removeClass("vis");
	this.butMenuClicked = 0; 
   	
	// remove handler for window.resize event to avoid permanent event handling 
	$(window).off("resize");
	
	console.log("class vis removed");

};


// --------------------------------------------------------------
// function declarations 
// --------------------------------------------------------------

/**
 * Function to create all CtrlOs in the GOC
 * more is not required - the rest is done by methods of CtrlOs 
 */
function page_init() {
	
	var GOC_name = "Anracon_GOC";  
	// CtrlO creation 
	this[GOC_name].populate(); 
}


//----------------------------------------------------------------------------
// Executable statements - after all declarations are known to the interpreter 
// ----------------------------------------------------------------------------

// We now create our own container for all CtrlOs 
// required to control the user interaction with elements of our web page  
this.Anracon_GOC = new Anracon_GlobalObjectController(this);

 

A Global Object Controller [GOC] object

The first thing we do is to create a global object “Anracon_GOC” – a Global Object Controller [GOC] – which shall control a variety of so called “Control Objects” (CtrlOs) for defined regions, i.e. containers, of a web page. A CtrlO contains all required functionality for all interactive GUI elements of the controlled container. Thus, we avoid clattering the global context with functions and variables.

Actually in our case we only need one CtrlO object for our menu container “div#main_menu_cont”. But I have left some parts of method code relevant for the treatment of multiple CtrlOs of more complex pages. The names of all CtrlOs which shall be created should be listed in the array “ay_objNames” of the constructor function “Anracon_GlobalObjectController()” for the GOC-object. Constructor functions of the form “Ctrl_NameoftheCtrlO” must be defined for all CtrlOs, which we want to handle. The name NameoftheCtrlO is the related name saved in the array “ay_objNames”. Prefixes are defined for both the constructor functions and the CtrlO objects.

The prototype “method” defined as “Anracon_GlobalObjectController.prototype.populate” creates all CtrlO objects defined. Do not get confused by the indirect addressing in the statement for object creation – this was done for convenience, i.e. for an automatic assignment of the right object names and the choice of the right constructors based on defined prefixes and the contents of our object name array. You may be astonished that the “new” operator is not followed by a literal. Actually the “new” operator in JS requires the address of a constructor function object – this is in our case provided as the relevant function reference in the global context (remember: a function in JS is itself an object!).

As the CtrlOs must in complex web pages be able to interact with each other, a further “method” defined in Anracon_GlobalObjectController.prototype.pushObjectReferences of the GOC dispatches references of all created CtrlOs to each of the CtrlOs. It is not necessary to discuss details here as we only have one CtrlO to care for.

The method defined as Anracon_GlobalObjectController.prototype.loadCssFile loads the mentioned additional CSS file for mobiles. This method is called in the GOCs constructor in case that the function defined in the file “detectmobilebrowser_jQuery.js” had set a variable of the jQuery object – namely jQuery.browser.mobile – to true. The url is the path or address of
the CSS file to load. The early loading of the additional CSS file helps to prevent page flickering flickering.

Addendum 09.09.2015: We have added some parameters and code to loadCssFile() to show what would have to be done, if we wanted to replace teh original CSS file instead of appending an additional one.

The CtrlO “Anracon_GOC.CtrlO_Hmenu

The object “CtrlO_Hmenu” is based on the constructor function “Ctrl_Hmenu(objName)”. It provides the desired functionality for our main menu container. In our test case the only interactive element is our menu button. The constructor first defines selectors for certain elements of the menu and also the text container in the right column of our layout (see the previous articles).

The first relevant action is to remove the “hover”-effect of our old JS-free solution by removing the defined CSS class “hover”, which we defined from the container “div#menu_cont”. This class triggered the “mouseOver” based remote rollover effect discussed in our last article. No such class – no “hover” and no possible negative impact on our aspired “click”-based control for the menu.

In the next step we display a message in case that we detected a mobile browser. We do this in the right column of our layout. The last step consist of a delegation of the functionality for handling a “click” on the menu-button to a method displayHmenu of the object CtrlO_Hmenu.

This is done by registering the method as a callback with the $.proxy() function of the jQuery object ($). This way we guarantee that the context of the this operator in the callback called during the click event is automatically switched from the event’s HTML object (our menu button “a#hmen_but”) to our pure JS object CtrlO_Hmenu.

If we had not used $.proxy() the context of “this” in displayHmenu() would have been the menu button. (A typical error ….) Read about the right context assignment in the documentation of $.proxy() and other articles on the Internet if you are in doubt.

The method “displayHmenu()” of the CtrlO “CtrlO_Hmenu”

The method displayHmenu() does some tricky things during the click event of “div#hmen_but”. This has to do with the priority of CSS properties set by JS. See the discussion above. The correct behavior is only achieved for “var method=0” – the alternative code is only for purposes of testing of a wrong solution.

To understand the problem of CSS priority let us do a thought experiment and assume that we directly changed the visibility property of “div#hmen_cont” by JS in course of a mouse click event:

First imagine that in the width Range I we just switched the visibility property of our menu after a click on the button to “visible”. No problem: The menu would appear as a vertical block – just as expected and due to our CSS rules for Range I. If we then resized the browser window again to bigger widths (viewport width Range II) the menu would stay visible a while as a vertical block – but at the threshold to Range II it would change its appearance from vertical appearance back to the regular a horizontal menu line. So far so good.

However, what happened if we in Range I clicked a second time on the button and this time used Javascript to change the visibility of the menu to “visibility:hidden”? This is exactly what a user expects as a standard interaction with a toggle button. Then during resizing of the browser window to a bigger width (on a desktop by dragging the window edge with the mouse or in a smartphone by rotating the device to a horizontal orientation) the menu would stay invisible forever and quite independent of the final
viewport width range. This is due to the priority of the CSS property change performed by JS! The same would by the way be true for other property changes directly set by JS. Such a behavior is, of course, not what we want!

We circumvent this problem by not setting properties directly but by loading and unloading an extra class “.vis

div#hmenu_class.vis { visibility:visible;}

for “div#hmenu_cont” at the click-event of”div#hmen_but. See the code.

Now we stumble into the next problem: The unloading should also occur for a resize event of the window – even if we did not click a second time on the button. Otherwise the menu would remain visible to a transition to Range II and a successive transition back to Range I. No harm done – but it does not feel right: when we go to Range I the menu should always disappear and only be shown after a button click.

So, we have to react to the “window.resize” event and unload the class “vis” as soon as this event occurs. But for saving CPU power we want to unload only once and NOT to react all the time to the resize events which fire permanently during window size changes. To achieve this we use the following trick:

When clicking the first time on the menu button we load the class “vis” – and at the same time activate an event handler for the resize event by using jQuery’s $(window).on() function. At a subsequent click we unload the class “vis” and at the same time switch off the event handler by jQuery’s .off() function. We encapsulate the latter 2 steps in a method Ctrl_Hmenu.prototype.removeClassVis of our CtrlO.

Conclusion

This was really a long tour. But, actually, we have realized all our objectives. We have in addition seen that the efforts in our previous articles were not at all in vain. We integrated JS smoothly with the ability to automatically eliminate the few, possibly interfering CSS definitions for the Non-JS solution. Thanks to this approach, we now have a solution for browsers with and without activated Javascript. At least in principle we have also shown how we could react to mobile devices in a more specific way by loading appropriate CSS sheets to adjust font-sizes and other CSS properties.

As we did all JS in an object oriented way our JS code is now prepared to include further methods to control the user interaction in a structured form. We shall make use of this already in the next article. In the meantime you may want to play around with our present example with desktop and mobile browsers:

Address for our test example with Javascript:
https://linux-blog.anracom.com/examples/fluid/fluid_standard2.html
Address for the same example without JS:
https://linux-blog.anracom.com/examples/fluid/fluid_standardx.html

In the next forthcoming article of this series

Responsive fluid multi-column layouts – sub menu adaption with JS/jQuery – VI

we shall deal with the left sided sub menu in a more flexible and user friendly way.

Responsive fluid multi-column layouts – with a responsive horizontal menu bar – IV

In the previous articles of this series

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

I described how we can control HTML div order problems which may interfere with a simple, efficient approach to make a 3 column fluid layout responsive to the viewport width. I regard this topic as sufficiently covered: By planning the HTML code and its DIV order properly we can make responsiveness a matter of changing just a few CSS properties (like float) for defined viewport width ranges in our CSS code. Small CSS tricks restore the optical layout if we have to choose a special and unusual DIV order of (horizontally) floated or relatively positioned DIV containers in the HTML code for achieving a specific vertical element order after a responsive transformation.

Nevertheless, I do not want to close this article series about responsive layouts without looking into methods for a more suitable handling of the horizontal main menu. Actually, up to now we did not care about the main menu at all. The floated menu points just move downwards over several rows if the viewport width gets to small to display the menu in just one line.

Some people may find such an approach uneconomical, looking weird and not at all a professional solution for small viewport widths, e.g. on a smartphone. For a real responsive (horizontal) menu the wide spread expectation is that the whole menu collapses at small viewport widths – in parallel to the vertical reordering of major page areas.

In this and the next article we shall have a look at some options how to achieve a better responsive menu control – without and with Javascript.

Objectives for an originally horizontal main menu at small viewport widths

In the second article of this series we defined 3 viewport width ranges. “Range I” stands for small viewport widths which may be typical for smartphones. In this range the main menu would only be interesting if the user really needs it – otherwise it consumes too much of the limited screen area and may enforce unnecessary scrolling.

Therefore, it is reasonable to collapse the horizontal menu to a button (with a reasonable menu symbol) within Range I. This is exactly what e.g. many wordpress designs and other web tools do: Below a certain width threshold menus are reduced to symbols with button functionality. A click on such a button would open the main menu – now with vertically listed menu points as we do not have much width available. A second click on the button – and/or other actions which we need to discuss – would close the menu again.

This description seems to imply that Javascript [JS] is required for a convenient handling of a collapsed menu. More precisely: for a convenient dealing with user induced GUI-events for some specific elements of our web page. Therefore the following question is of major interest: What alternatives do exist for users who have deactivated Javascript?

In this article we concentrate one a JS-free approach. Some readers may not find the suggested solution convenient enough; but the efforts in this article to build a flexible horizontal menu which gets collapsed are not at all in vain – all HTML and CSS code can and will be used almost unchanged in a JS-based solution. See a forthcoming article for this.

Restructuring the horizontal menu on the HTML level

The
horizontal menu as we described it in the previous articles was set up in a most simply way: It was just a list with floated elements – positioned on the left side in its container “div#head” due to defaults. The CSS statements offered no options to center the menu or put it on the left or right side of the page. However, such options – in the form of easily changeable CSS properties – may be important in certain web layouts. In addition we need a “menu button” which shall be displayed for small viewport width. Two good reasons for restructuring the main menu a bit …

The new HTML code for the container “div#head” appears much more complex than in the previous articles – but you will understand the advantages of the DIV nesting in a minute.

HTML code for the horizontal main menu

<body onload="init_page();">
<div id="all">
	<div id="head">
		<div id="main_menu_cont">
			<div id="bg_hor_menu"></div>
			<div id="menu_cont">
				<a id="hmen_but" title="main menu" href="#"></a>
				<div id="hmen_cont" class="hmen_cont">
					<div id="bg_ver_menu"></div>
					<ul id="menu_lst">
						<li><div class="hmen_pkt"><p><a href="#">h-menu 1</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 2</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 3</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 4</a></p></div></li>
						<li><div class="hmen_pkt"><p><a href="#">h-menu 5</a></p></div></li>
						<li class="hm_li_right"><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
						<li class="floatstop"></li>
					</ul>
				</div>
			</div>
			<p class="floatstop"> </p>
		</div>
	</div>
....
....
</div>
....
....

 
We encapsulate our menu now inside the main container “div#main_menu_cont” and supply a background element “div#bg_hor_menu” which allows for a full control of background and opacity effects in a “horizontal header line”.

Note: In the following CSS strategy this horizontal header line which comprises the menu outside width range I will still be shown when we collapse the menu contents.

To decouple the menu itself from the “header line” we introduce a second inner container “div#menu_cont”. It shall server 2 purposes:

  • It will help us to gain control about centering the menu – a list of points – inside the horizontal menu line despite a missing explicit definition of the menu width.
  • It is also required to control the interaction of the user with the menu for small viewport widths when Javascript is not available. This will become clear in some minutes.

“div#menu_cont” encloses the real content stuff of the menu for all width ranges. Among its new elements is a “menu button” – more precisely: a block A-tag “a#hmen_but” – which up to now has no functionality – and shall only be visible in viewport width Range I. As a matter of fact, in the JS-free solution we do NOT assign any functionality to this button – but instead to its container “div#menu_cont”! See below.

The container “div#hmen_cont” encloses in addition

  • a relatively positioned list “ul#menu_list”, which contains the floated menu points (LI-tags)
  • and an absolutely positioned “div#bg_menu” for controlling background effects (color, opacity) for the possibly horizontally centered and later on vertically displayed menu.

Note that in our test example the width of the menu list and also of “div#hmen_cont” will not be defined explicitly for viewport widths outside of Range I: The browser has to evaluate the menu width from the widths of its listed elements.

Centering the menu in the header line

The following CSS excerpt makes clear how we achieve a centering of the menu list in the header line.

....
....
/* The header region */	
/* *******************/

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

/* The main contents container */
/* *************************** */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	min-height: 10.0rem;
	margin-top:1.0rem;
	z-index: 1; 
}
....
....	
/* 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#main_menu_cont {
	display: block; 
	position: relative;
	width: auto;   
	margin-left: 1.0rem; 
	margin-right: 1.0rem; 
	min-height: 3.0rem;
}

div#bg_hor_menu {
	display:block; 
	position: absolute; 
	top: 0;
	bottom: 0;
	left: 0; 
	right: 0; 
	border-radius: 0.8rem;
	background-color: #FFF; 
	opacity: 0.75; 
	z-index: 1; 
}

div#menu_cont {
	display: block; 
	position: relative;
	float:right; 
	width: 100%;  
	min-height: 3.0rem;
	z-index: 2; 
	
	/* helps to center its enclosed container */
	text-align: center; 
}

a#hmen_but {
	display: none; 
	width: 4.6rem;
	height: 2.2rem;
	margin-top:0.2rem; 
	background-color: #A90000;
	border-top: 0.2rem #CCC solid; 
	border-right: 0.2rem #CCC solid; 
	border-left: 0.2rem #AAA solid; 
	border-bottom: 0.2rem #AAA solid; 
}

div.hmen_cont {
	position: relative;
	
	/* display: block; */  
	
	/* makes horizontal centering possible despite undefined width */
	display: inline-block; 
	
	min-height: 3.0rem;
	
	/* A second method of centering - optically determined by the viewport width */
	/*
	width: auto;
	margin-right:0.8rem;
	margin-left:0.8rem;
	*/
	
	border-radius: 1.0rem;
	
	/* centers the enclosed list ! */
	text-align: center; 
}

div#bg_menu {
	display:block; 
	position: absolute; 
	top: 0;
	bottom: 0;
	left: 0; 
	right: 0; 
	border-radius: 0.8rem;
	background-color: #FFF; 
	opacity: 1.0; 
	z-index: 1; 
}

div.hmen_cont ul {
	position: relative;
	
	/* makes horizontal centering possible despite undefined width */
	display: inline-block;
	list-style-type: none;
	/*width: 100%;*/
	margin: 0;
	padding: 0;
	padding-left: 0.4rem;
	padding-right: 0.4rem;
	z-index:2;
}

div.hmen_cont ul li, 
div.hmen_cont ul li.hm_li_left,
div.hmen_cont ul li.hm_li_right {
	float: left;
	padding: 0.2rem 4.0rem 0.2rem 4.0rem;
	margin:0; 
	margin-top:0.2rem;
	height: 2.2rem;
	/*border-left: #ffffff 1px solid;*/
	border-right: #a90000 0.2rem solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}

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

div.hmen_cont ul li a, 
div.hmen_cont ul li a:visited  {
	text-decoration: none;
 
	color: #000; 
}

div.hmen_cont ul li a:hover {
	color: #A90000; 
	background-color: #FFFFBB; 
}

a#but2 {
	display: block;
	width: 1.6rem;
	height: 1.6rem;
	background-color: #A90000;
	border-top: 0.2rem #CCC solid; 
	border-right: 0.2rem #CCC solid; 
	border-left: 0.2rem #AAA solid; 
	border-bottom: 0.2rem #AAA solid; 
}

 
Note that the new layer “div#menu_cont” is floated in its enclosing container “div#main_menu_cont”. This was done due to convenience reasons. In course of the transition to Range I we shall reduce the width of “div#menu_cont” and position it on the right side of the horizontal menu line. This is easily achieved by floating. If we wanted it to collapse to the left side in Range I we would just change the float direction. Thus floating “div#menu_cont” gives us a bit of positioning freedom whenever we should need it. As long as we set the width to “100%” outside Range I we do not need to take care of any centering of “div#menu_cont” there.

Note that we blend out the menu button (via “display:none;”) – as long as we are not in Range I (see below). The dimensions of the block A-tag will become important in Range I.

How exactly do we do the centering for the menu contents?
As the width of “ul#menu_lst” is undefined, we cannot use the usual trick with “margin-left:auto; margin-right:auto;”. Instead we need declare the block-element “ul#menu_lst” as an inline element via “display: inline-block;”. After this the CSS property “text-align:center;” of the enclosing container “div#hmen_cont” does its job.

Now the UL-tag is centered in its container. But “div#hmen_cont” has no defined width either. We therefore extend and cascade this type of centering one level higher – and declare also “div#hmen_cont” itself to be an “inline-block” element in its container “div#menu_cont”.

However, there are other possibilities to center dynamically on this outer tag-level:
One possibility could be to declare “div#hmen_cont” as a normal block element, but to set both its “margin-left” and its “margin-right” property to one and the same value. Then the visible width of “div#hmen_cont” and its background element “div#bg_menu” would become independent of the width of “ul#menu_lst”. Instead, the viewport width would determine the visible menu width. Such a centering would, however, not obstruct a further inner centering of the UL-tag within a wider “div#hmen_cont”! Try it out!

Note that instead of centering we could also move the menu containers to the right or left in their enclosing containers by using the property “text-align”. A bit of abstraction and combining different options on the 2 container levels will give us even more options to adapt to different optical layouts requested by our customers. The introduced intermediate containers open up for variety of centered and non-centered layouts of the horizontal menu.

For our next discussion points we shall only follow the line of a full centering. An impression of the resulting menu in Range III can be seen on the following picture.

responsive7

A transitional option to handle the horizontal menu

What about viewport widths between a typical desktop width of more than 1000 pixels and small viewport width around 400 to 500? In such an intermediate width range it is reasonable to first use the available space a bit better than just collapsing the menu directly. How you would adjust the width of each menu point depends of course on the way how you distributed them:

In our test example the original width of the horizontal menu was defined
by some “padding“-definitions for each the floated “LI”-tags in the horizontal menu (see the CSS above or in the last article). Therefore, an intermediate step could be to reduce the values for horizontal padding substantially when the viewport width shrinks below a threshold:

/* @media screen decision and settings for the horizontal menu */
@media screen and (max-width : 860px) {
	div.hmen_cont ul li {
		padding: 0.2rem 1.2rem 0.2rem 1.2rem;
	}
} 

In our test case this actually helps until the final transition to smallest viewport widths of Range I occurs:

responsive8

and

responsive9
 
Some warnings:
What looks nice in the pictures actually depends in real web layouts on a proper choice of the width threshold for the transition to smaller padding-values – we want to shrink the padding-values only when the menu width gets too big for the available viewport width. But the menu width in our example depends in turn mainly on the font settings for the page, e.g.:

@CHARSET "UTF-8";

html {
	font-size:10px;
}

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

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

 
Therefore, setting a proper width threshold for which the responsive transition to smaller padding-values shall occur also depends on the chosen font – and under worst circumstances also on font settings for the user’s browser. You see: some effort and testing for different fonts (especially for Google fonts if Android devices are the target client) is waiting for you. (At least if you do not want to depend on Javascript. JS would give you many more possibilities to analyze the situation dynamically.)

As a side effect we again see that a container policy with expandable containers in height is extremely important in responsive designs …. (we followed such a policy so far): If a browser replaced our carefully chosen fonts by some strange font and the available horizontal space got too small then we still had a fallback: We would display the menu with several rows without disturbing the rest of our page. The next pic shows a provoked situation of this type:

responsive10

You should at least follow the rule: Always set the threshold width for shrinking the distance between menu elements with some safety margins in mind! Already some slight differences in the display of fonts may lead to different behaviors of different browsers! Always test your responsiveness for a font which is set with relatively wide letters (like Verdana) first to be on the safe side.

Furthermore: In more realistic cases than our test example you may need additional thresholds and related successive padding- and/or margin-adjustments. Note that due to automatic margin-/padding-settings for list elements
in some browsers you may have to set margin-values explicitly in addition to controlling the menu width.

How to make a horizontal menu responsive ? Some general alternatives …

The general choices for dealing with a horizontal menu in responsive layouts are in my opinion:

  • Solution 1:Allow for horizontal menus with two or more lines in a certain viewport width range.
  • Solution 2:Adjust the width of the menu elements of a horizontal main menu – by reducing padding/margin-definitions or by introducing percentage widths of the menu elements – below certain viewport width thresholds.
  • Solution 3:Collapse the horizontal menu to a button below a defined viewport width threshold (without or with Javascript support for mouse interactions with the button).
  • Solution 4:Change from a horizontal to a vertical menu and position it properly.

The first solution was shown in the last articles. The second solution was briefly presented above.

At some point of the viewport width, however, you probably want and need to collapse the whole menu. The resulting menu button would then be attached to a vertical menu area whose visibility is controlled by mouse interactions with the button. This means that solution 3 is directly connected with solution 4. However, option 4 could in some cases (i.e. for Javascript free solutions) also be used as a totally independent solution.

After this overview we now concentrate on a collapse solution with a button in our defined viewport width “Range I” – without making use of Javascript [JS].

Collapsing the main menu to a button without Javascript support

A JS free solution may be useful for people who have JS deactivated. Although, without click-events on a button-like element it may be a bit more difficult to control the touch screen interaction with smartphones and tablet-PCs.

Fortunately, the browser developers for smartphones have understood this. E.g. both Firefox and Chrome for Android translate “rollover” events into something very similar to pure “click”-events. Therefore, the following solution should work even in browsers for (Android) smartphones:

CSS statements for a collapsing menu with remote rollover

/* @media screen decision and settings for the horizontal menu */
	
@media screen and (max-width : 860px) {
	
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right
	 {
		padding: 0.2rem 1.2rem 0.2rem 1.2rem;
	}
} 
.....
.....

/* @media screen decision and settings for range I */
/* ----------------------------------------------- */

@media screen and (max-width : 539px)  {
....
.....    
	/*
	* Treatment of the horizontal menu 
	*/
	
	div#bg_hor_menu {
		opacity: 1.0;
	}
	

	div#menu_cont {
		margin-right: 4.0rem; 
		width: 5.0rem;  /* button size ! */
		height: 3.0rem;
	}
	
	a#hmen_but {
		display: block; 
	}


	div#menu_cont:hover div.hmen_cont {
		visibility: visible; 
	}
	
	
	div.hmen_cont {
		position: absolute;
		visibility:hidden; 
		top:3.0rem;
		right:0; 
		min-height: 3.0rem;
		
		width: auto;
		min-width: 18.4rem;
		max-width: 25.0rem;
		
		margin-right:0rem;
		margin-left:auto;
		
		padding: 0.8rem; 
		
		background-color: #DDD;
		border-radius: 1.0rem;
		
		text-align: left; 
	}
	
	div#bg_menu {
		background-color: #DDD;
		opacity: 1.0;  
	}
	
	
	div.hmen_cont ul {
		position: relative;
		list-style-type: none;
		width: 
auto;
		margin: 0;
		padding: 0;
	}
	
	div.hmen_cont ul li, 
	div.hmen_cont ul li.hm_li_left,
	div.hmen_cont ul li.hm_li_right {
		float: none;
		padding: 0.2rem 0.4rem 0.2rem 0.8rem;
		width:auto;
		/*border-left: #ffffff 1px solid;*/
		border-right: #a90000 0.0rem solid;
		min-height: 2.0rem;
		font-size: 1.6rem;
	}
		
	div.hmen_cont ul li.floatstop {
		float:none;
	}

	div.hmen_cont ul li p {
		margin-top:0; 
		line-height: 1.6rem; 
	
	}

}    

/* extreme case - reached e.g. by Ctrl + */
@media screen and (max-width : 260px)  {
	div#menu_cont {
		margin-right: 4.0rem; 
	}
	
	div.hmen_cont {
		right: -4.0rem;
		width: auto; 
		min-width: 0; 
	}
	
	div#bg_menu {
		background-color: #DDD;  
	}
	
}	

 
The horizontal menu line “div#main_menu_cont” and the enclosed background DIV “div#bg_hor_menu” remain visible in all viewport width ranges. But with the transition to Range I we give up the centering and collapse the menu instead. This is done by a sequence of measures:

  • In Range I we make the menu button “a#hmen_but” visible. (In our example just a simple red button).
  • The dimensions of “div#menu_cont” are collapsed down to the size of the menu button. The (floated) container is moved a defined distance away from the right border of its container by setting a right-margin.
  • The menu container “div#hmen_cont” is optically hidden (via: “visibilty:hidden;”)
  • The menu container “div#hmen_cont” gets an absolute position, a minimum and a maximum width. The maximum width is required to enforce line wrapping for long menu points and is adjusted to a further viewport shrinking via an additional width threshold condition.
  • Color and opacity of the menus background layer are adjusted such that the menu region will clearly be visible above other layers. In z-direction the visibility on top of other layers is already guaranteed by the high z-index value assigned to div#head (see above).
  • Floating of the list elements is stopped. This gives us a vertical list of menu points.
  • We define a further width threshold and adjust properties of “div#hmen_cont” for extremely small viewport widths.

Note: The situation of an extreme small viewport width can be provoked in 2 ways in a browser:

  • Firstly by reducing the physical width of the browser window.
  • Secondly via a relative rescaling of the basic font size and adjusting all elements. This happens “Ctrl +” is used in a browser – i.e. by the zooming functionality of the browser in connection with our “rem” CSS-scaling of all dimensions. Everything is recalculated then with relative factors. And relative to the increased font-size the viewport width goes down. So the browser initiates the defined transitions.

The second possibility is the reason why we have to guarantee a reasonable behavior of our web page for even extreme small viewport widths. This explains our final safety transition: @media screen and (max-width : 260px) {}.

The next picture shows our “collapsed” menu in form of a pure button in viewport width Range I:

responsive11

Triggering the visibility of the menu after the collapse to a button – without JS

The reader may have asked already: How shall a user get access to the new vertical menu which is set to “visibility:hidden;
” in Range I? To achieve this we use a simple “trick” called “remote rollover”:

We move the mouse over an element and another element at a very different position reacts to this rollover. Our “div#menu_cont” and its logically enclosed “div#hmen_cont” provide a typical example:

div#menu_cont:hover div.hmen_cont {
		visibility: visible; 
	}

 
The pseudo class “:hover” of an container element may trigger CSS properties of an enclosed (absolutely positioned) element at an optical different position. In our case the hover triggers the visibility of its enclosed and now absolutely positioned container div.hmen_cont. The hover actually occurs on all visible elements of the container – independent of where they are located. So, if we could move the mouse from the area of “div#menu_cont” directly into the visible area of “div#hmen_cont” without moving outside one of these containers the triggered visibility of “div#hmen_cont” will sustain. Firefox and Chrome realize this CSS feature properly – so we can use it here.

The next picture gives you an impression of the “rollover” effect:
responsive12

Exactly what we wanted to achieve. You may also test in Firefox or Chrome that the button and the vertical menu really are vertically “connected” despite the little optical vertical gap between them:

You can move the mouse from the button vertically down into the menu below without loosing contact and visibility. Once visible the lower area remains so if we move the mouse vertically downward in a channel of the button width. This is due to the fact that the upper edge of “div#hmen_cont” seamlessly touches the lower edge of “div#menu_cont”. [If you really wanted or needed a major and visible vertical distance between the two Divs: This can be achieved by introducing a transparent, properly dimensioned and absolutely positioned bridge element, i.e. an additional more or less transparent and absolutely positioned DIV inside “div#menu_cont”.]

However, as soon as the mouse in our example leaves either the area of the button or the area of the still visible menu then the visibility of the menu is lost. So, to make the menu disappear again the user just has to move the mouse outside the connected visible areas of the “menu button” and the menu itself.

What happens in a smartphone browser? See the picture for an Android 5 smartphone in horizontal orientation with relatively low resolution.

IMG_quer

OK – range II is chosen. Now what about the button behavior? We rotate the smartphone:

hoch_ohne_menu

At least in FF or Chrome you can just press your finger (similar to a “click”) on our menu button – and our vertical menu region will appear.

hoch_mit_menu

Then you can “click” on any menu point – the menu remains visible. To make the menu disappear the user just
has to “click” – i.e. press the finger – on some page area outside the button or the displayed menu. Nice, isn’t it?

Again a warning:
Although this is a fully functional solution a normal user will find it unusual or even frustrating that a second click on the red button does not hide the menu again. Without JS we unfortunately have no remedy for this. And the page gets not unusable.

Still: We have reached our main objectives. So far, we have only used CSS – no Javascript at all. Nevertheless we have realized a menu collapse with a rollover functionality throughout our responsive viewport width transition.

Of course you can use all the little tricks discussed above also for (left sided) submenus. We come back to this point in a later article of this series.

Alternatives?

There is always the trivial alternative for users who have deactivated JS to transform the menu into a permanently visible vertical menu – but with a “position:relative;” inside the then expanding header line. Such a solution would be very similar to what we did with the left sided menu in the last article. We do not need to show code examples. Such a solution would, however, consume even the same or more space at the top of the web page than a simple floating solution with several menu lines. Its only advantage over a floating solution is that it may look a bit more organized.

A supplemental hint regarding scrollbars

All our main relatively positioned containers scale with the viewport width due to a setting of “position:relative; width: 100%;”.
This may not be the case in realistic layouts. If you have a layout where the main page elements do not expand with the viewport above a certain width threshold, but have a maximum width instead, you should always use the following CSS setting

width: 100%;
max-width: your_max_width_value;

and not just

width: your_max_width_value; /* above the threshold */

for all outmost containers inside the body and where necessary also for enclosed elements. Do not forget to do this also for images which shall be scaled below a width threshold set for the named max-width of the elements.)

This type of setting is required to give the browser a chance to deal in a reasonable way with vertical scrollbars and a restructuring of the page elements when the scrollbar is displayed. Note that the viewport width includes the width scrollbar to be displayed. I.e. the scrollbar is part of the viewport. So changes of page elements during responsive transitions may only occur when the distance between the left browser edge and the right outer (!) edge of the scrollbar fits to the viewport width threshold value. So some elements may be covered and hidden by the scrollbar before the CSS statements below the width threshold get operative.

Therefore, if you just switch the CSS properties for containers to “width: 100%;” below the width threshold a vertical and an additional (superfluous) horizontal scrollbar may appear. If you instead use the given definition above only the vertical scrollbar will appear because the browser then can rearrange the displayed contents as soon as the vertical scrollbar appears and even when it moves inside the width threshold.

Conclusion

Even without JS a solution is achievable with a collapsed menu at small viewport widths. It is fully functional though maybe not exactly how the user may expect it.

In the forthcoming article


Responsive fluid multi-column layouts – main menu control with JS/jQuery and font-size adaption –
V

we shall discuss how we introduce some simple Javascript functionality into the menu handling.

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

In the previous articles of this series

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

we discussed a problem that may arise when you want to make a fluid design responsive.

In order

  • to both use a fluid design with multiple content columns in horizontal direction and
  • and to control the vertical heights of optical background elements for the different columns

you need to encapsulate floated page elements in a container.

Such floated content elements could be a vertical navigation column, a main information block with text and a right column with teaser text elements. In your HTML code you would define and describe the tags of the floated container elements in just this natural sequence. The CSS properties of the containers require no special attention – everything is straightforward. However, the natural tag order may lead to conflicts when you want to reposition the column like page elements in a fluid and additionally responsive approach to our multi-column layout.

Typically, in a responsive approach you rearrange the content columns into a vertical order instead of the original horizontal sequence, because you want to make use of a maximum of the view port width for each of the information and navigation containers. For reasons of coding efficiency the vertical rearrangement of previously horizontally distributed containers should be achieved just by changing some CSS properties like “position”, “margin” or “float” when the view port gets smaller than some predefined values. Under normal circumstances you do NOT want to create major HTML block elements twice and switch them alternately on and off just to become responsive.

For getting the right vertical ordering effect you would e.g. make use of the automatic handling of floated elements by the browser for situations when the horizontal viewport space gets too small for displaying all floated elements in just one row. In such a situation, however, the last tag (of the sequence of floated elements) in the code represents the element which is moved downwards in the browser’s viewport first.

In the second article of this series we therefore discussed a special situation for which we required a specific vertical reordering of existing elements. We showed how we could re-nest and rearrange the DIV-container tags into the required code order – without changing the original optical and fluid layout. Of course we had to compensate by more complicated CSS definitions.

Among other things we used negative margin definitions to optically place floated elements where we needed them to be on the screen – despite an unusual tag position in the HTML code. With a bit of abstraction the discussed recipe will in general allow for any required tag order for the DIV-containers in the code. What we have achieved is that by using some compensating CSS tricks we can now choose a certain – unusual, but in a responsive context required – tag order in the HTML code without destroying the optical features and the fluidity of our multi-column page layout.

We have demonstrated how to handle a first responsive transition in viewport width by moving one of the columns downward and adapt our content areas in width – without loosing fluidity. In the last article we also discussed some additional, conventional measures to make the different content column blocks appear as separate optical elements floating above a common background (picture).

We have, however, not yet shown how to manage a second – and more
important – transition to a range of significant smaller view port widths. For small view port widths we would like to move our left sided vertical menu directly below the text area – an objective we had already defined in the first article and which will be met by the measures discussed in the present article.

We shall see in this article that our achievements now allow for almost trivial measures to gain the correct aspired responsiveness: You just need to change a few “float” and “margin” properties.

To make everything a bit more challenging we shall also show how one can add transparent background layers to each column of the original fluid layout and how we manage such transparency layers together with responsive width adaptions of the web page.

Extending the HTML code by tags for transparency layers

We firstly define DIV containers which serve us as transparent background stripes of all column elements of our fluid design. To be able to keep up the transparency during a later responsive element reordering we need to insert such background layer (DIV) elements

  • into the defined outmost container “div#outer_cont” and the additionally introduced internally nested container “div#main_cont”,
  • and as well as into each of the floated DIV-elements representing the text column blocks.

Due to some simple CSS rules already presented in the previous articles the absolutely positioned background layers will adapt automatically to the height of their encapsulating DIV containers (via “position: absolute; top:0; bottom:0;” settings).

The height of the encapsulating containers is in turn dynamically defined by the maximum height of their enclosed relatively positioned, but floated text containers – i.e. according to the available horizontal space, the text content length and the height of other enclosed inner elements.

The modified HTML code is here:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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="bg_left0"></div>
		<div id="bg_right0">
			<!-- do not remove ! -->
			<div id="bg_right_inner0"></div>
		</div>
		<div id="bg_info0"></div>
		
		<div id="float_cont">
			<div id="main_cont">
				<div id="bg_left1"></div>
				<div id="bg_info1"></div>
				
				<div id="info_cont">
					<div id="float_info">
						<div id="bg_info2"></div>
						<div 
id="info"> 
							<p>Lorem ipsum .....</p>
							<p> </p>
							<p>Lorem ipsum ....</p>
							<p> </p>
							<p>This is the end, my friend<br> </p>
						</div>
					</div>
					<div id="left_nav">
						<div id="bg_left2"></div>
						<div id="left_inner">
							<ul>
								<li><p><a href="#">sub menu point 1</a></p></li>
								<li><p><a href="#">sub menu point 2</a></p></li>
								<li><p><a href="#">sub menu point 3</a></p></li>
							</ul>
						</div>
					</div>
					<p class="floatstop"> </p>
				</div>
			</div>
			
			<div id="right">
				<div id="bg_right2"></div>
				<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>

 
Note: If you want to test our example in some smartphone do not forget a reasonable meta-tag like

<meta name=”viewport” content=”width=device-width, initial-scale=1″>

to the header of the HTML file. You may adapt the metatag given in the above code to your needs. For more information see e.g.:
https://css-tricks.com/snippets/html/responsive-meta-tag/

The reader may have noticed that we have changed some element names in comparison to the HTML code discussed in the last article – but this not be a big obstacle as we kept up the basic tag nesting structure.

The multiple defined internal background layers (bg_left0, bg_left1, bg_left2, bg_info0, bg_info1, bg_info2, bg_right0, bg_right2) MUST be switched/on off at certain width values for the viewport. See below.

Extending the CSS definitions to cover a second transition of the view port width – and transparency effects

We need to adapt the CSS definitions to handle the transparency layers properly – for all view port width transitions. In the 1st article we had defined 3 ranges of view port widths – therefore we have to cover 2 transitions. In the last article we have shown the definitions for the first transition from Range III to Range II.

In addition, we now need to take care of a second width transition from Range II to Range I. At this second transition we want to move the left sided menu area downwards – right below the main text area and on top of the originally right column area (see the 2nd article). All 3 text areas ( i.e. our original columns) shall use all the width of the view port – we only leave small border areas transparent.

You find the necessary, adapted CSS definitions here:

@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
	margin-left:0; 
	margin-right:0;
	margin-top: 0; 
	background-image: url(../image/hg_straa_mp.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#bg_left0, 
div#bg_left1  {
	width: 14rem;
}

div#left_nav {
	margin-left: 1.0rem;
	
}

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


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

div#bg_left0, 
div#bg_left1,
div#bg_left2,
div#bg_right0,
div#bg_right_inner0,
div#bg_right2,
div#bg_info0,
div#bg_info1,
div#bg_info2  {
	position: absolute;
	top:0;
	bottom:0;
	border-radius: 0.8rem;
}

div#bg_left0 {
	left:1.0rem;
	background-color: #FFF;
	opacity: 0.5;
	border: 0px solid #F00;
	z-index:1;
}

div#bg_info0 {
	position: absolute;
	left:16.0rem;
	right:27%; 
	background-color: #FFF;
	opacity: 0.85;
	border: 0px solid #00F;
	z-index:1;
}

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

div#bg_right_inner0 {
	left: 1.0rem;
	right: 1.0rem;
	background-color: #FEEEBB;
	opacity:0.80;
}



	
/* 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:1;
}


div#bg_left1 {
	display: none;     
	left:1.0rem;
	background-color: #FFF;
	opacity: 0.75;
	border: 0px solid #F00;
	z-index:1;
}

div#bg_info1 {
	display: none; 
	left:16.0rem;
	right:1.0rem; 
	background-color: #FFF;
	opacity: 0.9;
	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%; 
	/*background-color: #99e;*/ 
	border: 0px solid #FF0000;
	z-index:2;
}


div#info { 
	position: relative; 
	margin: 0 27% 0 16rem;
	width:auto;
	/*background-color: #0f0;*/ 
	padding:0.8rem;
	/*height:200px;*/
	z-index: 1;
}

div#bg_info2 {
	display: none; 
	left:1.0rem;
	right:1.0rem; 
	background-color: #FFB;
	background-color: #FFF;
	opacity: 0.95;
	border: 0px solid #00F;
	z-index:1;
}




/* 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 {
	position: relative; 
	width:auto;
	padding: 0.8rem;
	z-index: 2; 
}

div#bg_left2 {
	display: none;     
	left:1.0rem;
	right:1.0rem; 
	border: 0px solid #F00;
	background-color: #FFF;
	opacity: 0.9;
	z-index:1; 
}



/* right column */
/* ------------ */

div#right { 
	float:left;
	position:relative; 
	margin-left: -27%;
	min-height: 2.0rem;
	/*min-width: 15.2rem;*/ 
	/*background-color: #eee;*/ 
	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;
	z-index: 2;
}

div#bg_right2 {
	display: none;     
	left:1.0rem;
	right:1.0rem; 
	border: 0px solid #F00;
	background-color: #FEEEBB;
	opacity: 0.85;
	z-index:1; 
}



/* 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: #EEE;
	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-left: #ffffff 1px solid;*/
	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: 1.8rem;
	height: 1.8rem;
	background-color: #A90000;
	border-top: 0.2rem #CCC solid; 
	border-right: 0.2rem #CCC solid; 
	border-left: 0.2rem #AAA solid; 
	border-bottom: 0.2rem #AAA solid; 
}



/* contents of the left vertical menu */

#left_inner ul {
	width:100%;
	min-height: 2.2rem;
	list-style-type: none;
	padding: 0;
	margin: 0;
}

#left_inner ul li {
	width:100%; 
}

#left_inner ul li a, 
#left_inner ul li a:visited {
	color: #990000;
	
}
#left_inner ul li a:hover {
	color: #FFF;     
}
    
    
/* ---------------------------------------------- */    
    

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

@media screen and (min-width : 540px) and (max-width :828px) {
    
	div#info { 
		margin: 0 2.0rem 0 16rem;
		background-color:transparent;
	}
   
	div#right { 
		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;
	}
    
	/*
	 * Switch OFF backgrounds after 1st viewport width transition 
	 */
	div#bg_left0 {
		display: none;  
	}
	div#bg_info0 {
		display:none;  
	}
	div#bg_right0 {
		display: none; 
	}
    
    
	 /*
	  * Switch ON backgrounds after first viewport width transition 
	 */
	 
	 div#bg_left1 { 
		display: block; 
	 } 
	   
	 div#bg_info1 { 
		display: block; 
	 }
	 div#bg_right2 { 
		display: block; 
		opacity: 0.9;
	 }

}


    
/* ---------------------------------------------- */    
    

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


@media screen and (max-width : 539px)  {
    
	
	div#info { 
		margin: 0 1.0rem 0 1.0rem;
		/* background-color: #FFB; */
		border-radius: 0.8rem;
	}
	
	
	div#left_nav { 
		margin-top:1.0rem;
		margin-left: 0;
		width:100%;
		padding:0;
	}
	  
	div#left_inner {
		width:auto;
		padding: 0.8rem;
		margin: 0 1.0rem 0 1.0rem;
		/*  background-color: #DDDDDD; */
		border-radius: 0.8rem;
		z-index: 2; 
	}
	
	div#right { 
		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;
	}


   
	/*
	 * Switch OFF backgrounds after 2nd viewport width transition 
	 */
	
	div#bg_info0 { 
		display: none; 
	 }
	div#bg_info1 { 
		display: none; 
	 }
	 
	div#bg_right0 {
		display: 
none;  
	}
	 
	div#bg_left0 {
		display: none; 
	}
	div#bg_left1 { 
		display: none; 
	}   
	
	/*
	 * Switch ON backgrounds after 2nd viewport width transition 
	*/
	div#bg_info2 { 
		display: block; 
		opacity: 0.95; 
	}
	div#bg_left2 { 
		display: block; 
		opacity: 0.95; 
	}   
	div#bg_right2 { 
		display: block; 
		opacity:0.9; 
	}

}    

 

Before we discuss some details let us look at the visual results:

Range III: Starting point
responsive1

Range III: Fluidity when reducing viewport width
responsive3

Range II: Move the rightmost column to the bottom
responsive4

Range I: Move the leftmost column right below the text box and over the initially right column
responsive5

A closer look may reveal that we changed the transparency a bit during the transitions between the viewport width ranges.

Some CSS hints

We switch some of the transparency layers off and on during the range transitions by using the “display” property. This is somewhat saver than just changing the visibility.

As soon as a major content element is moved downwards during a responsive action we switch off the corresponding background element in its encapsulating container. Instead, we switch on the respective background layer in that container which becomes relevant for the correct height of the background layer after the vertical repositioning.

Thus, in Range III bg_left0, bg_info0, bg_right0 are the active background layers. In Range II, however, we use
bg_left1, bg_info1 and bg_right2. In Range I we activate the innermost layers bg_left2, bg_info2, bg_right2.

The height of these absolutely positioned background containers layers is defined by the height of their outer encapsulating container. The height of the latter is defined by the highest of its inner relatively positioned content containers. This type of indirect height control of absolutely positioned layers via relatively positioned boxes on the same code level inside one and the same encapsulating container is basic CSS2.

The transition between Range II and Range I is done by mainly 2 adaptions in the section

@media screen and (max-width : 539px) {}

of the CSS code:

  • We adapt the margins of “div#info” to a full extension over the viewport width -i.e by setting both “margin-left” and “margin-right” to small values. ( Small values to keep some optical distance of the contents to the viewport edges …). This change of margins is already enough to vertically reposition “div#left_nav” if we at the same time change the property “left-margin” of “div#left_nav” (from -100%) to zero.
  • n

  • We set the width of “div#left_nav” to 100%.

Pretty simple, isn’t it?

All the rest is cosmetics. Note, that the opacity values are adapted during the transitions, too. Such an adaption may be helpful if elements of the background image started to disturb the readability of your text during the repositioning of the web page’s text containers.

What if we had wanted to move the left sided navigation column above the main text box in Range I?

This is a relevant question. But after what we have learned so far the answer is extremely trivial. So, I leave this up to the reader.

Addendum 25.08.2015:
As I got a question regarding this point I give a hint. The most simple solution builds on the following steps: Change the tag order of the floated “div#left_nav” and “div#float_info” in the HTML code. Adapt the margins of div#left_nav (no loner a negative margin-left, but a negative margin-right!). Change widths and margins – also margin-top – at the responsive transition as required.

Note that turning “div#float_info” a right floated div would not help much. The tag that is the last one in the code for floated elements of a container represents the first element which is moved downwards by the browser as soon as the widths of the floated elements collide. By performing such an experiment we would again see that the div-order in the code is of major importance. However, we are now able to adapt such an order quickly to our requirements regarding the final responsive outcome.

What about “inline-block” instead of floated elements?

Also a good point. With inline block elements [CSS property “display:inline-block;”] we can indeed replace floated elements in their containers. However, the tag order plays the same role for the behavior of inline-block elements as for floated elements when the space for a horizontal presentation becomes insufficient. The last tags are moved downwards. So we would have to work with the same type of tricks as discussed in this and the last article.

What is still missing?

Although we have harvested

  • control over the DIV order in the code required for an easily manageable responsiveness,
  • control over responsive adaptions of the position and width of content containers and their transparent background layers

there are of course still things to improve. E.g.:

  • The upper horizontal main menu could be collapsed to a button with a switch ON/OFF functionality on the upmost area of the web page.
  • The vertical navigation sub-menu could also be collapsed to a kind of button available on the left side of the viewport.

A full “Switch ON/OFF” functionality for such menu button requires Javascript. However, there is a well defined – though limited – solution also for users which have Javascript deactivated. These new objectives will be topics of forthcoming articles.

In the next article of this series

Responsive fluid multi-column layouts – with a responsive horizontal menu – IV

we shall describe a handling of the horizontal main menu line at small viewport widths – without involving Javascript.