A closer look at the max-width property

I came across an interesting issue today with the max-width property. It’s a property that I’ve only used in a few occasions in the past – mainly due to the constraints of legacy browsers that do not support it.

That being said, it’s extremely useful and this week I’ve found an interesting peculiarity with it that I have not come across before. Searching for this issue proved to be fruitless – mainly due to the fact that it’s a bit of a tricky one to word properly!

The issue I was finding in all browsers that support min-width (with the exception of IE7) was how it handled the width when a text label exceeded the allotted max-width value and thus was forced to wrap onto a subsequent line(s).

For example, I had a series of 7 navigational items. No item in the list was to exceed 104 pixels in width. This worked, as expected, but where I saw the ‘funniness’ were on the navigational elements that required word-wrapping (since they were to wide to fit on a single line). In these cases, the max-width value of the item was still being applied as 104 pixels even though the item was now technically less than that in width. This created really ugly gaps between the items, as you can see from the screen-shot below:

Whitespace gaps in the navigation caused by the max-width property

In order to get some context on the issue I was having, I’ve provided the HTML and CSS I used:

<ul id="primary" class="clearfix">
	<li><a href="about_e.html">About us</a></li>
	<li><a href="products_e.html">Investment products</a></li>
	<li><a href="prices_e.html">Prices and performance</a></li>
	<li><a href="management_e.html">Investment management</a></li>
	<li><a href="informed_e.html">Stay informed</a></li>
	<li><a href="helpful_e.html">Helpful information</a></li>
	<li><a class="lit" href="investor_e.html">Investor learning</a></li>
</ul>
#primary {
	font-size: 14px;
	line-height: 1;
	list-style-type: none;
	margin: 0;
	position: relative;
	word-wrap: normal;
	z-index: 100;
}

	/**
	* @subsection Custom-styled for "Home" Page
	*/
	#primary li {
		display: inline-block;
		font-family: Arial, Helvetica, sans-serif;
		margin: 0 0 0 20px;
		max-width: 7.43em; /* 104 pixels */
		padding: 10px 0;
		vertical-align: middle;
		*display: inline;
		*zoom: 1;
	}
		#primary a {
			display: inline;
			text-decoration: none;
			text-transform: uppercase;
		}

		#primary :hover a,
		#primary .sfhover a,
		#primary a.lit,
		#primary a:hover { color: #eaac1f !important; }

Note: This was taken from the existing project so a few extraneous styling rules may have been missed (that were elsewhere in the CSS document), but for the most part this is what I had.

I thought this was a bug until I cracked open a copy of the W3C specification and found the following:

  1. The tentative used width is calculated (without ‘min-width’ and ‘max-width’) following the rules under “Calculating widths and margins” above.
  2. If the tentative used width is greater than ‘max-width’, the rules above are applied again, but this time using the computed value of ‘max-width’ as the computed value for ‘width’.
  3. If the resulting width is smaller than ‘min-width’, the rules above are applied again, but this time using the value of ‘min-width’ as the computed value for ‘width’.

Item number two from the list above is where my problems were stemming from. For all intents and purposes, the max-width properly was working the way it was supposed to (which means that IE7′s version of the property is incorrect, even if it meant that it worked the way I was hoping it would).

In terms of finding a solution with CSS it would appear I was out of luck. Short of rewriting the specification for the property and having it ok’d and implemented cross-browser before this project I was working on was due, well, that wasn’t going to happen! :)

The solution to my problem was to use a bit of JavaScript that ran through each item in the navigation and then assigned a width to the item (the one that has been assigned the max-width property) that was equal to the width of the hyperlink contained therein. Since this width value of the hyperlink could not possibly exceed the max-width value, I knew that the highest the value could go was 104 pixels and the lowest would be however wide the now reformatted (word-wrapped) hyperlink was:

<script type="text/javascript">
	$('#primary > li').each(function() {
		$(this).width($('a', this).width());
	});
</script>

This rendered the navigation in the way I wanted – each item that was previously set to the max-width value was now re-sized to be the actual width of it’s child element, as shown in the screen-shot below:

Whitespace gaps in the navigation removed by JavaScript function

Notes

  • The hyperlink had to be styled as an inline-level element; otherwise if was styled as a block-level element the width of it would always be equal to the width of it’s parent.
Posted in CSS | Tagged , , | Leave a comment

CSS3 Lessons: Multiple Background Images

The CSS3 background property has been completed enhanced from it’s CSS 2.1 predecessor, with some really interesting new features that I plan on investigating in future articles. For the purposes of this article, I’ll be looking at the multiple image ability of the background-image property.

The following lesson is based on the W3C Working Draft (June 12, 2010) of CSS Backgrounds and Borders Module Level 3.

The CSS2 Problem: Single Background Images

Rounded corner feature boxAs shown on the right, this is just a simple static content feature for the client’s Facebook and Twitter accounts. The layout and decoration of the feature, however, is a bit more complicated. Add to the fact that this element should have a fluid width and height, and you are looking at a very ‘heavy’ piece in terms of the HTML, CSS and imagery that are required to achieve this.

Using this example, it is clear that there will be five background images needed, as well as five HTML elements on which we can layer the images onto:

  • Rounded corner, top left
  • Rounded corner, top right
  • Rounded corner bottom left
  • Rounded corner with shadow and gradient, bottom right
  • Small icon of globe and speech bubbles

We can be creative and combine these images into a single sprite, but alas the same cannot be said for the HTML – we require the same number of elements as we have background images:

<div class="feature">
	<div class="tr">
		<div class="br">
			<div class="bl">

				<h2>Join the Conversation</h2>

				<p>
				Lorem ipsum dolor sit amet, consectetur
				adipiscing elit. Nunc mollis nunc suscipit arcu
				viverra vitae posuere eros dictum.
				</p>

				<p>
				<a href="#" target="_blank"><img alt="Facebook" height="17" width="79" src="/labs/CSS3/lessons/_images/logo_Facebook_79w_17h.png" /></a>
				<a href="#" target="_blank"><img alt="Twitter" height="22" width="93" src="/labs/CSS3/lessons/_images/logo_Twitter_93w_22h.png" /></a>
				</p>

			</div>
		</div>
	</div>
</div>

The alternative is to force the element to be a static width and thus cut down on the amount of HTML required. This method allows you to define just a single background image (plus an extra one for the green icon) which is tall enough to allow the element to grow vertically without any display issues occurring.

In the case of the project I was working this element into, I went with the static width approach as I wanted to keep the markup as clean as possible. The problem with this approach, however, is when the feature needs to be shown within multiple columns of differing widths. I avoided HTML’s ‘tag soup’ only to jump into a nice bowl of CSS ‘soup’!

Of course, neither solution mentioned above is desirable, but our hands are tied with the constraints of the CSS 2.1 specification, so either we dumb down the design or  go HTML/CSS ‘crazy’.

The CSS3 Solution: Multiple Background Images

Note: Before I get into the CSS3 solution to this problem, I want to clarify that I understand using the border-radius property could achieve close to the effect shown above, but for the purposes of this tutorial I am looking for a purely background image based solution.

Creating the required background images

Generating the required (5) images was the first step, and so much like what has been done in the ‘old day’s, a image was sliced for each rounded corner (4) and then one for the green ‘globe’ icon. Since their is a subtle gradient (running bottom to top) in the design, the way I sliced this up was like so (the guides are there to show the dimensions of each image):

Screenshot of the background image boundaries for the feature

Note: The ‘globe’ icon was sliced out separately as an PNG-24.

The resulting images that were created, based on the diagram above, are as follows:

  • Top left (30×30)
    Top left corner of the rounded feature
  • Top right (251×30)
    Top right corner of the rounded feature
  • Bottom left (30×142)
    Top left corner of the rounded feature
  • Bottom right (251×142)
    Bottom right corner of the rounded feature

The HTML markup

With the enhancements to the background properly that CSS3 provides, the markup required to create a rounded-corner feature like this is greatly reduced from what was previously required in the HTML source example provided above. Since CSS3 allows for multiple background images, it means that we are no longer need to define an HTML element for each background image that the design requires. In other words, all the background images we need can be defined on a single element.

<div class="feature">

	<h2>Join the Conversation</h2>

	<p>
	Lorem ipsum dolor sit amet, consectetur
	adipiscing elit. Nunc mollis nunc suscipit arcu
	viverra vitae posuere eros dictum.
	</p>

	<p>
	<a href="#" target="_blank"><img alt="Facebook" height="17" width="79" src="/labs/CSS3/lessons/_images/logo_Facebook_79w_17h.png" /></a>
	<a href="#" target="_blank"><img alt="Twitter" height="22" width="93" src="/labs/CSS3/lessons/_images/logo_Twitter_93w_22h.png" /></a>
	</p>

</div>

So much cleaner – and easier for an author to integrate into their content.

Styling the HTML markup with CSS3

The styling for the feature is very similar to the way this type of feature was done in the past. First, we need to define the general layout of the element before applying the background images (generated in the steps above) to it.

View the first step in the process here. Remember to view source to get the HTML and CSS

Next, using the multiple background image enhancement that CSS3 delivers, we simply map each of our images to the <div class=”feature”> element – positioning each in it’s proper location.

To do this, simply define each of the background images and separate them with a comma, like so:

div.feature {
	background-image: url(_images/layout_feature_top_left_30w_30h.gif),
		url(_images/layout_feature_top_right_251w_30h.gif),
		url(_images/layout_feature_bottom_left_30w_142h.gif),
		url(_images/layout_feature_bottom_right_251w_142h.gif),
		url(_images/icon_globe_51w_49h.png);
}

The order in which the background images are declared is important as the first is the top-most layer while the last one is at the bottom. In other words, you could think of the first background-image (layout_feature_top_left_30w_30h.gif) having a greater z-index than the last one (icon_globe_51w_49h.png)

View the second version of the feature here.

You’ll note that the only image visible is the first one – which is due to the fact that it is being repeated horizontally and vertically. Since none of the images should be repeated, we can simply issue a background-repeat property of no-repeat and this will apply to all the defined background images. There is no need to define a background-repeat: no-repeat 5 times, as is explained here in more detail (first two paragraphs).

The third version of the feature visible shows multiple images being applied to the feature box- that is if your browser supports this property!

The final step is to position each of these images to the appropriate location (x, y). Again, we can either define one position for all, or individually define each image’s position, which in this case is what we need to do, in a similar format to the background-image property we defined above:

div.feature {
	background-position: 0 0, 100% 0, 0 100%, 100% 100%, 100% 0;
}

How cool is that eh? One <div> element handling all five background images! This box can be as wide as we want, and can house as much content as we want – it is completely fluid.

Just one small problem – where did that green logo go? If you viewed the first five examples, there was no sign of it, but if you viewed the last example you could partially see it. Why is this? Remember the images are stacked from top (first image defined) to bottom (last image defined). In this example, the logo was defined last, so in order to make it visible we can just define it first and bump the other four images down one level, like so:

div.feature {
	background-image: url(_images/icon_globe_51w_49h.png),
		url(_images/layout_feature_top_left_30w_30h.gif),
		url(_images/layout_feature_top_right_251w_30h.gif),
		url(_images/layout_feature_bottom_left_30w_142h.gif),
		url(_images/layout_feature_bottom_right_251w_142h.gif);

	background-position: 95% 7%, 0 0, 100% 0, 0 100%, 100% 100%;
}

Also, since the order of the images is shifted, that means the background-position property will need to be updated as well to reflect this change.

And there you have it, the completed rounded corner feature box that utilizes five background images within a single <div> element. View the final example here.

Posted in CSS3 Lessons | Tagged , , , , , , , , | 6 Comments

Is your video content getting indexed?

Read an interesting article the other day called Video SEO: A technical guide (by Joost de Valk) which goes into a in-depth explanation of video SEO. It is something that I had not really thought about previously – had really just been concentrating on getting individual pages to be indexed by leading search engines like Bing or Google. However, if your site makes use of video content, it is really in your best interest to ensure that it is indexed (viewable).

In Joost’s article, he mentions four methods of getting your video content indexed. The method I ended up choosing was an XML video sitemap. I originally tried the RDFa method, but the WordPress blog platform I’m on kept stripping out all the meta data that was inserted into the <object> tag of the embedded video. This actually was the method I preferred, but I couldn’t find a way to get it to work with the WordPress editor.

To test this out, I used an annoucement video that my employer (High Road Communications) released back in January of this year.

I created an XML file, based on the documentation found on Google’s webmasters site and than submitted it as a sitemap within Webmaster Tools. The XML file that I created looked like the following:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
	<url>
	  <loc>http://www.lifeathighroad.com/web-development/can-google-see-your-video/</loc>
	  <video:video>
	    <video:title>High Road Announcement Video</video:title>
	    <video:publication_date>
	      2010-01-19
	    </video:publication_date>
	    <video:content_loc>

http://www.youtube.com/v/lVT2Q1g02X0&amp;hl=en_US&amp;fs=1?hd=1

	    </video:content_loc>
	    <video:thumbnail_loc>

http://i1.ytimg.com/vi/lVT2Q1g02X0/default.jpg

	    </video:thumbnail_loc>
	    <video:description>
		By pushing past the boundaries of conventional public relations with new ideas and original thinking well connect you with the media and influencers that matter. Its all about your brand. Map your companys future with High Road and go forward.
	    </video:description>
	    <video:tag>High Road Communications</video:tag>
	    <video:tag>iStudio</video:tag>
	    <video:tag>annoucement</video:tag>
	    <video:duration>68</video:duration>
	  </video:video>
	</url>
</urlset>

You can get a description of all the various tags by viewing the documentation here on the webmaster’s site referenced above.

Update: RDFa now working

I was able to find a way of getting that RDFa method to work using a nifty plugin called EmbedIt. I’ve updated the <object> tag of my video so that it now has the required RDFa markup, as documented on SearchMonkey:

<object
	classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
	width="640"
	height="385"
	codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"
	rel="media:video"
	resource="http://www.youtube.com/v/lVT2Q1g02X0"
	xmlns:media="http://search.yahoo.com/searchmonkey/media/"
	xmlns:dc="http://purl.org/dc/terms/">

		<param name="allowFullScreen" value="true" />
		<param name="allowscriptaccess" value="always" />
		<param name="src" value="http://www.youtube.com/v/lVT2Q1g02X0&amp;hl=en_US&amp;fs=1?rel=0&amp;hd=1" />
		<param name="allowfullscreen" value="true" />
		<embed type="application/x-shockwave-flash" width="640" height="385" src="http://www.youtube.com/v/lVT2Q1g02X0&amp;hl=en_US&amp;fs=1?rel=0&amp;hd=1" allowscriptaccess="always" allowfullscreen="true"></embed>

		<a rel="media:thumbnail" href="http://i1.ytimg.com/vi/lVT2Q1g02X0/default.jpg" />

		<span property="dc:date" content="2010-01-19" />
		<span property="dc:description" content="By pushing past the boundaries of conventional public relations with new ideas and original thinking well connect you with the media and influencers that matter. Its all about your brand. Map your companys future with High Road and go forward." />

		<span property="media:title" content="High Road Announcement Video" />
		<span property="media:duration" content="68" />
		<span property="media:width" content="640" />
		<span property="media:height" content="385" />

</object>

I’m really curious to see the effect implementing these two methods will have in terms of visibility of the video. Will keep you posted!

Posted in Web Development, Web Services | Tagged , , , , , , | Leave a comment

A manageable method for creating CSS image sprites

The technique for creating and using CSS image sprites has been around for quite some time now. However, they don’t seem to be used near enough, despite the performance bonuses they can deliver. Perhaps it is due to the fact that front end developers feel that they take to much time to create or are to hard to manage. Whatever the case may be, the truth is that they are simple to make and not as annoying to mange as one would think.

In this post I’d like to go over the steps that I use when creating CSS image sprites for any front end development work that I am involved in.

There are a number of online tools out there that can help you with creating these, but when it comes down to it, the simplest method is using the tool you already have – Adobe Photoshop. I find it a bit humorous that there are all these ‘helper’ applications out there that try to aid you in creating image sprites – going as far as allowing you to create image slices around the regions you want to designate a particular style for. Last time I checked, Photoshop has a slightly better interface than some random Web application . just sayin’!

So enough with the trash talking and onto the methods that I use when creating image sprites for my front end development work on client Web/Intranet sites. I’ll be using the current High Road Communications design for this example.

  1. Identify elements from the design that will be included within the sprite. I normally only create sprite for images that elements that are going to be styled with a defined height and width value. Yes, it’s possible to do this for ‘fluid’ elements, but I find it can be a headache and not worth the time and/or performance benefits.

    Screenshot of the home page of High Road Communications

  2. Within Photoshop (I use CS2), I create a new PSD file with decent enough dimensions to fit all the images that will be included in the sprite.

    Create a new PSD in Photoshop

  3. From the original design I select each element that I have identified from step 1 and paste them into the newly-created sprite PSD. For each image that is inserted, I create an image slice for it (if you’re using Photoshop CS4 or higher there is no image slicing tool, so use Fireworks CS4+ instead).

    Adding an image to the sprite PSD 

  4. On the same layer that the image was pasted onto, I draw a thick border around it with the brush tool; using a really ugly color that will stand out. The reason I do this is for one, it is a good visual clue for where an individual sprite is in the PSD – having them all jammed together can get really chaotic in a short time. Secondly, when styling an element with CSS, it will be pretty easy to see when I have wrong background-position values used, as the thick border will show up in the page layout.

    Outline each image in the sprite

  5. When this process is complete, the sprite is saved out in an appropriate web format. I normally use PNG-8 as it has the best file compression, for the most part. In addition, I also save the sprite as a PSD so it can be easily managed if new images are added or old ones removed/updated.
  6. The last step is the CSS, of course! I style up all my elements as if they were referencing an individual image, and not a sprite, although I ensure that the background-image points to the sprite that was saved in the step prior. When that is complete, all that remains is to update the x and y coordinates for the background-position properly of each element that is referencing the sprite, as well as the actual width and height values. This is where the image slices that were created for each element in the sprite (step 3) comes into play. When viewing the properties for a particular slice it will give you all the information you need – x and y position, and the width and height values. Plug those into your CSS and you are complete!

    Slice details for a particular image in the sprite 

So there you have it, the process of creating an image sprite is not only easy, but fairly manageable as well. As long as you keep your image slices updated when making any changes to the sprite, determining the height, width, x and y values will not be a problem at all.

Posted in Quick Tips | Tagged , , , | 3 Comments

Demo: Dynamic Cross-browser Horizontally Fluid Menu

The following is a solution that I came up with create a fluid horizontal navigation structure that met the following requirements:

  • The navigation needed to be completely dynamic and not dependent on an update to a CSS file if items were added or removed from it.
  • Must horizontally span the entire width defined by the page layout.
  • Each navigational item has to be horizontally and vertically centered within it’s ‘cell’.
  • No ‘cheating’ by using a <table> for a layout construct.
  • Support as many browsers as possible – including IE6.

I spent some time trying to find a workable solution and I was surprised that I couldn’t find any that met most of what I needed. Perhaps thats is more of a testament to my searching “skills”? Anyhow, creating my own solution and re-inventing the proverbial wheel seemed more interesting to me anyways!

If browser support wasn’t an issue, this would have been a fairly easy task to complete – after all, you can simulate a table layout by using the display: table operation along with table-row and table-cell. However, neither IE6 or IE7 support these layout types.

Based on the requirements that this navigation had, doing a straight-up HTML and CSS solution was not going to work. It was pretty clear that there would need to be some DOM manipulation required via JavaScript.

The HTML and CSS for the navigation

For the site that I implemented this solution on, there was dropdown navigation for each of the ‘primary’ items. However, in my implementation for this example I’ll be assuming there is no dropdown navigation involved. However, this doesn’t really affect or change the way the elements will be styled – but there is one important thing to keep in mind which I’ll get to later.

<div id="wrapper">
	<ul class="navigation">
	<li><a href="#">Home</a></li>
	<li><a href="#">About the Company</a></li>
	<li><a href="#">News Releases</a></li>
	<li><a href="#">Investors Information</a></li>
	<li><a href="#">Policies and Producedures</a></li>
	<li><a href="#">Contact Us</a></li>
	<li><a href="#">Site Map</a></li>
	</ul>
</div>

Nothing to out of the ordinary – just a simple unordered list within a wrapper which will be used to define the width of the layout.

#wrapper {
	margin: 0 auto;
	width: 969px;
}

ul.navigation {
	background-color: #e1e1e1;
	list-style-type: none;
	margin: 0;
	padding: 0;
	overflow: hidden;
	position: relative;
	width: 100%;
}
	ul.navigation li { float: left; text-align: center; }
		ul.navigation a {
			display: block;
			color: #666;
			padding: 6px 10px;
			text-decoration: none;
		}
		ul.navigation a:hover { background-color: #f1f1f1; }

The styling of the navigation is kept very simple – I’m basically just floating each item in the navigation and then applying the necessary padding to the item’s hyperlink. To keep things simple and clean, it’s best to not apply any borders, padding or margins to the list items. Otherwise, the script which dynamically resizes them will need to account for this (box model), and currently it is not setup for that. I recommend that if you require extra elements with which to style your navigation with that you just use <span> tags within the <li> tag itself.

Demo 1: Simple horizontal navigation

A working example of the navigation created thus far can be viewed here.

This demo works well across all browsers and all the items in the navigation (depending on your default font size) fit on the same horizontal plane. If we disregard the requirement of the navigation spanning the entire width of the wrapper, this solution works fine.

Demo 2: Breaking the layout

Things change, however, when extra items are added and we can see rather quickly that a need for some DOM manipulation will be necessary, as shown in the example here.

True, this is a rather excessive amount of navigational items, but if you discount that fact and consider that the user could have a larger-than-defined-font-size, or, if the site is in multiple languages the labels for each item could be considerably longer, causing it to break the layout. Either way you look at it, this layout does not allow the navigation to be ‘dynamic’ – you would have to manually adjust the width of each of the <li> elements within the CSS.

Using a JavaScript-based solution

Using JavaScript to fix the layout problems is not a preferred way of doing things, but if this layout needs to work across a wide range of browsers that include IE6 and IE7 then it is the only way that it can be accomplished. Besides, this is a purely visual solution and will not impose any accessibility issues with users who do not have JavaScript enabled on their browser or handheld device (they’ll just see the navigation as was shown in the second demo).

The first thing to do is to include a reference to the jQuery library:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

Next, the actual JavaScript that formats the navigation to meet the requirements:

Array.prototype.sum = function() {
  return (! this.length) ? 0 : this.slice(1).sum() +
	  ((typeof this[0] == 'number') ? this[0] : 0);
};

function menu() {
	var nav = $('ul.navigation'),
		nav_item_height = 0,
		numMenuItems = $('> li', nav).size(),
		totalMenuItemWidth = 0,
		menuWidthRemainder = 0,
		wrapperWidth = 969,
		maxNavItemWidth = 200,
		priNavItems = [];

	/* First, determine the total width of each item in the navigation. */
	$('> li', nav).each(function (i) {
		totalMenuItemWidth += $(this).width();
		priNavItems.push(($(this).width() > maxNavItemWidth) ? maxNavItemWidth : $(this).width());
	});

	/* Primary navigation items (combined) are less width than the
	wrapper. */
	if(totalMenuItemWidth < wrapperWidth) {
		menuWidthRemainder = wrapperWidth - totalMenuItemWidth;

		$('> li', nav).each(function() {
			var tmp_width = $(this).width();
			$(this).width(tmp_width + parseInt(menuWidthRemainder / numMenuItems));
		});

		tmp_width = $('> li:first', nav).width();
		$('> li:first', nav).width(tmp_width + (menuWidthRemainder % numMenuItems));
	}

	/* Primary navigation items (combined) are greater than the width of
	the wrapper. */
	else if(totalMenuItemWidth > wrapperWidth) {
		var priResizedNav = [];

		for(var x = 0; x < numMenuItems; x++) {
			/* Always take the lowest value as the sum total of the resized
			navigation items must never exceed the wrapper width. */
			priResizedNav.push(Math.floor((priNavItems[x] / priNavItems.sum()) * wrapperWidth));
		}

		$('> li', nav).each(function(i) {
			$(this).width(priResizedNav[i]).css('wordWrap', 'break-word');
		});

		$('> li:first', nav).width(priResizedNav[0] + (wrapperWidth - priResizedNav.sum()));
	}

	// Ensure top-level navigation spans the available width.
	$('> li', nav).each(function (i) {
		nav_item_height = ($(this).height() > nav_item_height) ? $(this).height() : nav_item_height;
	});

	$('> li', nav).each(function (i) {
		if($(this).height() < nav_item_height) {
			// Default padding: 6/6 (top/bottom).
			var paddingTop = parseInt((nav_item_height - $(this).height()) / 2);
			var paddingBottom = parseInt((nav_item_height - $(this).height()) - paddingTop);

			$('a', this).css({
				paddingTop: paddingTop + 6 + 'px',
				paddingBottom: paddingBottom + 6 + 'px'
			});
		}
	});
}

The following sections (and demos) will take you through each part of the script and breakdown what I was trying to accomplish.

The sum prototype of the Array object (at the top of the source code) is extraneous to this solution and was picked up from a third-party source. I use this as a ‘helper’ throughout my solution.

Determining the width of the navigation

$('> li', nav).each(function (i) {
	totalMenuItemWidth += $(this).width();
	priNavItems.push(($(this).width() > maxNavItemWidth) ? maxNavItemWidth : $(this).width());
});

The first block of the script is used to determine what the actual width of each item in the navigation is at run-time. Each item’s width (pixels) is stored within an array as well as tracking  a running tally of the overall width that is defined in a separate variable.

When tracking the width of each item, the value of the maxNavItemWidth variable is used to determine if the current item’s width is greater than the variable’s value (currently defined as 200 pixels). This ensures that no single item in the navigation will be larger than that pre-defined value (200 pixels). The reason for this is if you have an item that has a very long string/label. Instead of taking up all the available real-estate (and causing the navigation to wrap onto multiple lines or create awkward spacing issues) it instead can span no wider than the value that has been defined with the variable.

If the determined width is less…

There is one of two paths the script will take once the width of the navigation has been calculated. In the case where the navigation has been found to be less than the wrapper’s width (which we defined in this example as being 969 pixels), the following occurs:

if(totalMenuItemWidth < wrapperWidth) {
	menuWidthRemainder = wrapperWidth - totalMenuItemWidth;

	$('> li', nav).each(function() {
		var tmp_width = $(this).width();
		$(this).width(tmp_width + parseInt(menuWidthRemainder / numMenuItems));
	});

	tmp_width = $('> li:first', nav).width();
	$('> li:first', nav).width(tmp_width + (menuWidthRemainder % numMenuItems));
}
  • First, calculate the amount of pixel space that is remaining in the navigation and assign this value to a variable (menuWidthRemainder).
  • Enumerate through each item in the navigation and assign it’s current width plus the extra width (the amount calculated by dividing it by the number of items in the navigation).
  • Note: The first item in the navigation also receives the remainder from the calculation preformed in the step above.

A working example of the navigation created thus far can be viewed here. You will see how the items are all ‘stretched’ so that they fill the entire width of the navigational area.

…if the determined width is greater.

For navigation that is greater in width than what is allowed by the wrapper, a slightly more complicated solution is needed.

else if(totalMenuItemWidth > wrapperWidth) {
	var priResizedNav = [];

	for(var x = 0; x < numMenuItems; x++) {
		priResizedNav.push(Math.floor((priNavItems[x] / priNavItems.sum()) * wrapperWidth));
	}

	$('> li', nav).each(function(i) {
		$(this).width(priResizedNav[i]).css('wordWrap', 'break-word');
	});

	$('> li:first', nav).width(priResizedNav[0] + (wrapperWidth - priResizedNav.sum()));
}
  • The script enumerates through the list of items in the navigation and generates a new width value that is stored within an array called priResizedNav. The value is determined like so:
    • Take the initial width and divide this by the total width (priNavItems.sum) of the navigation, giving us a percentage value. For example, if item 1 is 200 pixels wide and the total width of all the navigational items is 1000 pixels than I’ll get a value of 0.2, or in other words, the width of item 1 is 20% the width of the entire navigation.
    • With this calculated value (i.e. 0.2), I then apply it to the maximum width of the navigation as a whole, which in our demo is 969 pixels. In other words, the width of item 1 is 20% of 969 pixels which is equal to 193.8 pixels.
    • Lastly, since this value in most cases, as in our example, is a decimal, I just take the integer value and round down (Math.floor) to ensure the resized values of the navigation don’t exceed the allowed total width.

A working example of the navigation created thus far can be viewed here.

You’ll recall earlier in this post that I mentioned that I had created this solution for a menu that had dropdown navigation. You may have wondered why I simply didn’t just apply a style of overflow: hidden to the <li> element in order to get around the expanding box-model problem that IE6 has. The reason I don’t is if I do that, the dropdown navigation gets ‘chopped off’ and is no longer visible since the overflow has been set to be hidden.

The last thing that this script needs to accomplish is to resize the height of each item in the navigation so that they are all vertically centered.

Adjusting the vertical height of the navigation

When the navigation spills outside the defined width of the wrapper, there are going to be item(s) that are on multiple lines and item(s) that fit on a single line. To have these items align vertically, the script below does the following:

$('&gt; li', nav).each(function (i) {
nav_item_height = ($(this).height() &gt; nav_item_height) ? $(this).height() : nav_item_height;
});

$('&gt; li', nav).each(function (i) {
if($(this).height() &lt; nav_item_height) {
// Default padding: 6/6 (top/bottom).
var paddingTop = parseInt((nav_item_height - $(this).height()) / 2);
var paddingBottom = parseInt((nav_item_height - $(this).height()) - paddingTop);

$('a', this).css({
paddingTop: paddingTop + 6 + 'px',
paddingBottom: paddingBottom + 6 + 'px'
});
}
});
  • Enumerate through each item in the navigation and determine which one has the greatest height value (in pixels).
  • Re-enumerate through the navigation and apply this height value (obtained from the step above) to each item if it is less than the calculated height.
  • Finally, apply padding-top and padding-bottom on the hyperlink within the item, using the same top and bottom values that are defined by the CSS.

View the final working example of the navigation, based on all the steps we’ve covered in this post.

Final notes and thoughts

So there you have it – a horizontally-fluid navigation that will dynamically resize to fit the items container therein. I look forward to your comments and suggestions on how I can improve this script, because I know there is a lot of room for improvement!

Posted in JavaScript, Labs | Tagged , , , , , , , | 3 Comments