2

I'd like to create a fluid horizontal navigation list of links, where the nav links are evenly spaced and take up the full width of the enclosing container . Nav links are all different widths. The first link should be left-aligned and the last link should be right aligned.

I've had limited success using techniques such as:

ul {display: table;width: 100%;}
li {display: table-cell;}
a {display: block;} 

and also using

ul {text-align: justify}
li {inline-block }

but no code I've written seems to deal at all well with elements that are different widths. The spacing does not seem to stay equal as the nav is resized.

I need the nav to be fluid, first and last elements to be flush against the edge of the containing ul and for the elements to be equidistant from each other

Mike Rifgin
  • 10,409
  • 21
  • 75
  • 111
  • Can you post a simple image showing what you want the navigation elements to look like? or post some code. – Marc Audet May 11 '13 at 22:17
  • Will the number of links be fixed? or variable? – Marc Audet May 11 '13 at 22:18
  • Number of links would be fixed. Basically it would need to look like this: http://img547.imageshack.us/img547/9901/screenxa.png - furthest left and right elements stick to the sides and all other elements are spaced evenly - even when resizing... – Mike Rifgin May 11 '13 at 23:44
  • Thank you! image nicely done, quite clear now, will take a look later, a bit later here. This is a useful little problem, worth some effort! – Marc Audet May 12 '13 at 00:21
  • It is quite a tricky issue. Want to know if anyone can think of a css fix. Perhaps, if not it could be achieved using some JS – Mike Rifgin May 12 '13 at 09:42
  • @MikeRifgin: Is it okay if there's dead space between the menu items that's not clickable? In other words, if the extra space that's added between the menu items to justify them isn't itself within a hyperlink. – Matt Coughlin May 12 '13 at 17:15

2 Answers2

5

I thought about this for a while and came up with two reasonable approaches, both of which are pretty good but not exactly pixel perfect. One is CSS based only and the second is aided by jQuery (JavaScript).

CSS Approach - pretty good approximation

Consider the following HTML:

<ul class="nav ex1">
    <li class="first"><a href="#">Home</a></li>
    <li><a href="#">Collections</a></li>
    <li class="tight"><a href="#">About Us</a></li>
    <li><a href="#">Slocklists</a></li>
    <li class="tight"><a href="#">Trade Enquiries</a></li>
    <li><a href="#">Press</a></li>
    <li class="last"><a href="#">Contact Us</a></li>
</ul>

I added some classes as hooks for the styling.

The CSS is as follows:

.nav.ex1 {
    outline: 1px dashed blue;
    width: 100%;
    margin: 0;
    padding: 0;
    display: table;
}
.nav.ex1 li {
    display: table-cell;
    outline: 1px dotted gray;
    width: 20%;
    white-space: pre;
    text-align: center;
}
.nav.ex1 li.first {
    width: 1%;
}
.nav.ex1 li.last {
    width: 1%;
}
.nav.ex1 li.tight {
    width: 1%;
}

In Example 1, the ul.nav parent container uses display: table and width: 100%. The child li elements are table-cell's. I added white-space: pre to prevent some of the links from wrapping into two lines, and text-align: center to keep the text centered.

The trick is to force some of the table-cell's to shrink-to-fit the text, and you can do this by setting width: 1% which is non-zero but too small to hold the text (unless your screen is 10,000 pixels wide). I shrink-to-fit the first and last cells which forces them to align to the left and right edges of the parent container. I then force every other table-cell to shrink-to-fit by added the .tight class.

The remaining table's cells will have a width of 20% which will keep them evenly spaced between their two nearest neighbors. HOWEVER, there will be some slight variation in spacing among the links in the row, which is why I call it an approximation.

jQuery Aided Solution

In Example 2, the markup is essentially the same and the CSS is:

.nav.ex2 {
    outline: 1px dashed blue;;
    margin: 0;
    padding: 0;
    display: block;
    overflow: auto;
    width: 100%;
}
.nav.ex2 li {
    float: left;
    display: block;
    outline: 1px dotted gray;
    width: auto;
}

In this case, the li elements are floated left and I use width: auto.

The trick is to calculate the magic left-margin value and apply it to all the li elements except for the first one.

The jQuery action is:

$(window).resize(function () {
    navResizer();
});

// On load, initially, make sure to set the size.
navResizer();

function navResizer() {
    var $li_w = 0;
    var $ul_w = $(".nav.ex2").innerWidth();

    $( ".nav.ex2 li" ).each(function( index ) {
        $li_w += $(this).innerWidth();
    });

    var li_margin = Math.floor(($ul_w-$li_w)/6);
    $(".nav.ex2 li").not(".first").css("margin-left",li_margin);
    $("p.note").text( "Widths: ul.nav: " + $ul_w + " all li: " + $li_w + " Left margin: " + li_margin);
}

Basically, the action calculates the width of ul.nav ($ul_w), and the total widths of all the li child elements ($li_w).

The left-margin is calculated by ($ul_w - $li_w)/6 where 6 is the number of gaps between the 7 links.

The key line of code is: $(".nav.ex2 li").not(".first").css("margin-left",li_margin);

I use .not(".first") to omit the first li element and then .css to set the left margin.

The one slight defect is at the far right where the link is not quite right justified, but you can fix that by floating the last li to the right.

For the most part, if your link texts were similar in length, you would be hard pressed to distinguish the two. Both approaches are not quite pixel perfect, but pretty good.

Fiddle: http://jsfiddle.net/audetwebdesign/xhSfs/

Footnote
I tried some other approaches using text-align: justify and inline-block, but the CSS engine does not treat inline-blocks like regular words, so will not justify a line of inline-blocks.

Setting left-margin to a % value will not quite work at some window widths and the right-most link will not be on the edge as desired.

The jQuery approach has been tried before, see:
Evenly-spaced navigation links that take up entire width of ul in CSS3

Community
  • 1
  • 1
Marc Audet
  • 46,011
  • 11
  • 63
  • 83
  • 1
    I like your solution with css tables but I think that using jQuery is overkill. Did you take the last line box into account when using `text-align: justify`? It is always left-justified. – Salomonder May 12 '13 at 18:39
  • jQuery is overkill, but it is nice to know that I can do it! I did try using `text-align: justify` but I missed the subtlety about it being jeft justified. Good point, comment appreciated. – Marc Audet May 12 '13 at 20:51
4

You can use text-align: justify; and ignore the last left-justified row. #last is invisible and takes up the last row because of padding-left: 100%;: http://jsfiddle.net/mwRbn/

if you want to align the text of the menu vertically, use height in combination with line-height:

ul#nav { 
  text-align: justify;
  width: 100%;
  background: yellow;
  height: 2em;
  line-height: 2em;
}

http://jsfiddle.net/mwRbn/1/. Do you need a IE<8 hack?

Salomonder
  • 336
  • 1
  • 6
  • I like this approach (+1) and I did not fully appreciate the subtlety of the last element with respect to justifying a line. However, the result is a two line navigation (the second line has no content) which may not always be desirable. – Marc Audet May 12 '13 at 21:12