tags:

views:

5021

answers:

8

You find plenty of tutorials on menu bars in HTML, but for this specific (though IMHO generic) case, I haven't found any decent solution:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • There's an varying number of text-only menu items and the page layout is fluid.
  • The first menu item should be left-aligned, the last menu item should be right-aligned.
  • The remaining items should be spread optimally on the menu bar.
  • The number is varying,so there's no chance to pre-calculate the optimal widths.

Note that a TABLE won't work here as well:

  • If you center all TDs, the first and the last item aren’t aligned correctly.
  • If you left-align and right-align the first resp. the last items, the spacing will be sub-optimal.

Isn’t it strange that there is no obvious way to implement this in a clean way by using HTML and CSS?

A: 

Make it a <p> with text-align: justify ?

Update: Nevermind. That doesn't work at all as I'd thought.

Update 2: Doesn't work in any browsers other than IE right now, but CSS3 has support for this in the form of text-align-last

Jordi Bunster
That was fast! I thought my own solution was unique, but you came up with something similar to start with in just a few moments.
flight
A: 

For Gecko-based browsers, I came up with this solution. This solution doesn't work with WebKit browsers, though (e.g. Chromium, Midori, Epiphany), they still show trailing space after the last item.

I put the menu bar in a justified paragraph. Problem is that the last line of a justified paragraph won't be rendered justified, for obvious reasons. Therefore I add a wide invisible element (e.g. an img) which warrants that the paragraph is at least two lines long.

Now the menu bar is justified by the same algorithm the browser uses for justifying plain text.

Code:

<div style="width:500px; background:#eee;">
 <p style="text-align:justify">
  <a href="#">THE&nbsp;MENU&nbsp;ITEMS</a>
  <a href="#">SHOULD&nbsp;BE</a>
  <a href="#">JUSTIFIED</a>
  <a href="#">JUST&nbsp;AS</a>
  <a href="#">PLAIN&nbsp;TEXT</a>
  <a href="#">WOULD&nbsp;BE</a>
  <img src="/Content/Img/stackoverflow-logo-250.png" width="400" height="0"/>
 </p>
 <p>There's an varying number of text-only menu items and the page layout is fluid.</p>
 <p>The first menu item should be left-aligned, the last menu item should be right-aligned. The remaining items should be spread optimal on the menu bar.</p>
 <p>The number is varying,so there's no chance to pre-calculate the optimal widths.</p>
 <p>Note that a TABLE won't work here as well:</p>
 <ul>
  <li>If you center all TDs, the first and the last item aren't aligned correctly.</li>
  <li>If you left-align and right-align the first resp. the last items, the spacing will be sub-optimal.</li>
 </ul>
</div>

Remark: Do you notice I cheated? To add the space filler element, I have to make some guess about the width of the menu bar. So this solution is not completely down to the rules.

flight
Note that you could have used a list here as well, if you set display:inline on the list-items... lists are more conventional for menus.
Andrew Vit
@Andrew Vit: You're right. That's mostly what asbjornu's solution suggests as well.
flight
A: 

I know the original question specified HTML + CSS, but it didn't specifically say no javascript ;)

Trying to keep the css and markup as clean as possible, and as semantically meaningful as possible to (using a UL for the menu) I came up with this suggestion. Probably not ideal, but it may be a good starting point:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&gt;

<html>

    <head>
     <title>Kind-of-justified horizontal menu</title>

     <style type="text/css">
     ul {
      list-style: none;
      margin: 0;
      padding: 0;
      width: 100%;
     }

     ul li {
      display: block;
      float: left;
      text-align: center;
     }
     </style>

     <script type="text/javascript">
      setMenu = function() {
       var items = document.getElementById("nav").getElementsByTagName("li");
       var newwidth = 100 / items.length;

       for(var i = 0; i < items.length; i++) {
        items[i].style.width = newwidth + "%";
       }
      }
     </script>

    </head>

    <body>

     <ul id="nav">
      <li><a href="#">first item</a></li>
      <li><a href="#">item</a></li>
      <li><a href="#">item</a></li>
      <li><a href="#">item</a></li>
     <li><a href="#">item</a></li>
      <li><a href="#">last item</a></li>
     </ul>

     <script type="text/javascript">
      setMenu();
     </script>

    </body>

</html>
David Heggie
+6  A: 

The simplest thing to do is to is to force the line to break by inserting an element at the end of the line that will occupy more than the left available space and then hiding it. I've accomplished this quite easily with a simple hr element like so:

<div id="menu">
  <ul>
    <li><a href="#">Menu item 1</a></li>
    <li><a href="#">Menu item 3</a></li>
    <li><a href="#">Menu item 2</a></li>
  </ul>

  <span></span>
</div>

The accompanying CSS is quite simple:

#menu {
  text-align: justify;
}

#menu * {
  display: inline;
}

#menu span {
  display: inline-block;
  position: relative;
  width: 100%;
  height: 0;
}

All the junk inside the #menu span selector is (as far as I've found) required to please most browsers. It should force the width of the span element to 100%, which should cause a line break since it is considered an inline element due to the display: inline-block rule. inline-block also makes the span possible to block-level style rules like width which causes the element to not fit in line with the menu and thus the menu to line-break.

You of course need to adjust the width of the span to your use case and design, but I hope you get the general idea and can adapt it.

asbjornu
**It seems to work if you use a `span` instead of an `hr`!** It's not really working, the HR is occupying visible space - use `#menu { border: solid 1px green; }` to confirm. Also, `display: inline-block;` does not work on IE (...7? CompatibilityView?) for elements that aren't naturally inline elements. HR is a block element, so I'm guessing inline-block doesn't work for HR on IE. Anyway, span.
ANeves
Also, make it `width: 100%;` instead of `width: 900px;`. Edit that and switch the hr for a span and you'll get an upvote - and most likely marked as answer. ;)
ANeves
I haven't tested my changes, but I think my solution should reflect your thoughts now.
asbjornu
@sr pt correct IE7 and lower. IE7 compatibility should not work either.
corymathews
A: 

Isn’t it strange that there is no obvious way to implement this in a clean way by using HTML and CSS?

Maybe, but are there any other layout languages that make this easy to implement? It might be harder than it looks.

Paul D. Waite
+2  A: 

Got a solution. Works in FF, IE6, IE7, Webkit, etc.

Make sure you don't put any whitespace before closing the span.inner. IE6 will break.

HTML:

<div class="outer">
<span class="inner">THE MENU ITEMS</span>
<span class="inner">SHOULD BE</span>
<span class="inner">JUSTIFIED</span>
<span class="inner">JUST AS</span>
<span class="inner">PLAIN TEXT</span>
<span class="inner">WOULD BE</span>
<span class="finish"></span>
</div>

CSS (you can optionally give .outer a width):

.outer {text-align: justify}
.outer span.finish {display: inline-block; width: 100%}
.outer span.inner {display: inline-block; white-space: nowrap}
mikelikespie
For me, this solution works fine with Gecko, but not with WebKit browsers (tested with Chromium, Midori, Epiphany): With WebKit, there's trailing space after the last item.
flight
Make sure there's only one character of whitespace between each span, and two between the last inner and the finish. If that doesn't work fiddle around with the whitespace. There is a bug in webkit.
mikelikespie
A: 
.outer {text-align: justify}
.outer span.finish {display: inline-block; width: 100%}
.outer span.inner {display: inline-block; white-space: nowrap}

Does not work on IE6 and IE7.

Binyamin
@Binyamin: This is no answer on its own, but a comment to mikelikespie's answer, right?
flight
@flight: Right!
Binyamin
A: 

if to go with javascript that is possible (this script is base on mootools)

<script type="text/javascript">//<![CDATA[
    window.addEvent('load', function(){
        var mncontainer = $('main-menu');
        var mncw = mncontainer.getSize().size.x;
        var mnul = mncontainer.getFirst();//UL
        var mnuw = mnul.getSize().size.x;
        var wdif = mncw - mnuw;
        var list = mnul.getChildren(); //get all list items
        //get the remained width (which can be positive or negative)
        //and devided by number of list item and also take out the precision
        var liwd = Math.floor(wdif/list.length);
        var selw, mwd=mncw, tliw=0;
        list.each(function(el){
            var elw = el.getSize().size.x;
            if(elw < mwd){ mwd = elw; selw = el;}
            el.setStyle('width', elw+liwd);
            tliw += el.getSize().size.x;
        });
        var rwidth = mncw-tliw;//get the remain width and set it to item which has smallest width
        if(rwidth>0){
            elw = selw.getSize().size.x;
            selw.setStyle('width', elw+rwidth);
        }
    });
    //]]>
</script>

and the css

<style type="text/css">
    #main-menu{
        padding-top:41px;
        width:100%;
        overflow:hidden;
        position:relative;
    }
    ul.menu_tab{
        padding-top:1px;
        height:38px;
        clear:left;
        float:left;
        list-style:none;
        margin:0;
        padding:0;
        position:relative;
        left:50%;
        text-align:center;
    }
    ul.menu_tab li{
        display:block;
        float:left;
        list-style:none;
        margin:0;
        padding:0;
        position:relative;
        right:50%;
    }
    ul.menu_tab li.item7{
        margin-right:0;
    }
    ul.menu_tab li a, ul.menu_tab li a:visited{
        display:block;
        color:#006A71;
        font-weight:700;
        text-decoration:none;
        padding:0 0 0 10px;
    }
    ul.menu_tab li a span{
        display:block;
        padding:12px 10px 8px 0;
    }
    ul.menu_tab li.active a, ul.menu_tab li a:hover{
        background:url("../images/bg-menutab.gif") repeat-x left top;
        color:#999999;
    }
    ul.menu_tab li.active a span,ul.menu_tab li.active a.visited span, ul.menu_tab li a:hover span{
        background:url("../images/bg-menutab.gif") repeat-x right top;
        color:#999999;
    }
</style>

and the last html

<div id="main-menu">
    <ul class="menu_tab">
        <li class="item1"><a href="#"><span>Home</span></a></li>
        <li class="item2"><a href="#"><span>The Project</span></a></li>
        <li class="item3"><a href="#"><span>About Grants</span></a></li>
        <li class="item4"><a href="#"><span>Partners</span></a></li>
        <li class="item5"><a href="#"><span>Resources</span></a></li>
        <li class="item6"><a href="#"><span>News</span></a></li>
        <li class="item7"><a href="#"><span>Contact</span></a></li>
    </ul>
</div>
Raksmey