如何在 HTML + CSS 中真正地证明水平菜单的合理性?

你可以在 HTML 的菜单栏中找到大量的教程,但是对于这个特殊的(虽然是通用的)案例,我还没有找到任何像样的解决方案:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • 有不同数量的纯文本菜单项,页面布局是流动的。
  • 第一个菜单项应左对齐,最后一个菜单项应右对齐。
  • 其余的项目应该在菜单栏上以最佳方式展开。
  • 数字是变化的,所以没有机会预先计算最佳宽度。

请注意,TABLE 在这里也不起作用:

  • 如果将所有 TD 都居中,则第一项和最后一项的对齐方式不正确。
  • 如果你左对齐和右对齐的第一响应。最后的项目,间距将次优。

这不是很奇怪,没有明显的方法来实现一个干净的方式使用 HTML 和 CSS?

86569 次浏览

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

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.

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">


<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>

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 span element like so:

#menu {
text-align: justify;
}


#menu * {
display: inline;
}


#menu li {
display: inline-block;
}


#menu span {
display: inline-block;
position: relative;
width: 100%;
height: 0;
}
<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>

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.

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.

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;
}
<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>

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>

Text is only justified if the sentence naturally causes a line break. So all you need to do is naturally force a line break, and hide whats on the second line:

CSS:

ul {
text-align: justify;
width: 400px;
margin: 0;
padding: 0;
height: 1.2em;
/* forces the height of the ul to one line */
overflow: hidden;
/* enforces the single line height */
list-style-type: none;
background-color: yellow;
}


ul li {
display: inline;
}


ul li.break {
margin-left: 100%;
/* use e.g. 1000px if your ul has no width */
}

HTML:

<ul>
<li><a href="/">The</a></li>
<li><a href="/">quick</a></li>
<li><a href="/">brown</a></li>
<li><a href="/">fox</a></li>
<li class="break">&nbsp;</li>
</ul>

The li.break element must be on the same line as the last menu item and must contain some content (in this case a non breaking space), otherwise in some browsers, if it's not on the same line then you'll see some small extra space on the end of your line, and if it contains no content then it's ignored and the line is not justified.

Tested in IE7, IE8, IE9, Chrome, Firefox 4.

This can be achieved perfectly by some careful measurements and the last-child selector.

ul li {
margin-right:20px;
}
ul li:last-child {
margin-right:0;
}

Ok, this solution doesn't work on IE6/7, because of the lack of support of :before/:after, but:

ul {
text-align: justify;
list-style: none;
list-style-image: none;
margin: 0;
padding: 0;
}
ul:after {
content: "";
margin-left: 100%;
}
li {
display: inline;
}
a {
display: inline-block;
}
<div id="menu">
<ul>
<li><a href="#">Menu item 1</a></li>
<li><a href="#">Menu item 2</a></li>
<li><a href="#">Menu item 3</a></li>
<li><a href="#">Menu item 4</a></li>
<li><a href="#">Menu item 5</a></li>
</ul>
</div>

The reason why I have the a tag as an inline-block is because I don't want the words inside to be justified as well, and I don't want to use non-breaking spaces either.

Simpler markup, tested in Opera, FF, Chrome, IE7, IE8:

<div class="nav">
<a href="#" class="nav_item">nav item1</a>
<a href="#" class="nav_item">nav item2</a>
<a href="#" class="nav_item">nav item3</a>
<a href="#" class="nav_item">nav item4</a>
<a href="#" class="nav_item">nav item5</a>
<a href="#" class="nav_item">nav item6</a>
<span class="empty"></span>
</div>

and css:

.nav {
width: 500px;
height: 1em;
line-height: 1em;
text-align: justify;
overflow: hidden;
border: 1px dotted gray;
}
.nav_item {
display: inline-block;
}
.empty {
display: inline-block;
width: 100%;
height: 0;
}

Live example.

Works with Opera , Firefox, Chrome and IE

ul {
display: table;
margin: 1em auto 0;
padding: 0;
text-align: center;
width: 90%;
}


li {
display: table-cell;
border: 1px solid black;
padding: 0 5px;
}

yet another solution. I had no option to tackle the html like adding distinguished class etc., so I found a pure css way.

Works in Chrome, Firefox, Safari..don't know about IE.

Test: http://jsfiddle.net/c2crP/1

ul {
margin: 0;
padding: 0;
list-style: none;
width: 200px;
text-align: justify;
list-style-type: none;
}
ul > li {
display: inline;
text-align: justify;
}


/* declaration below will add a whitespace after every li. This is for one line codes where no whitespace (of breaks) are present and the browser wouldn't know where to make a break. */
ul > li:after {
content: ' ';
display: inline;
}


/* notice the 'inline-block'! Otherwise won't work for webkit which puts after pseudo el inside of it's parent instead of after thus shifting also the parent on next line! */
ul > li:last-child:after {
display: inline-block;
margin-left: 100%;
content: ' ';
}
<ul>
<li><a href="#">home</a></li>
<li><a href="#">exposities</a></li>
<li><a href="#">werk</a></li>
<li><a href="#">statement</a></li>
<li><a href="#">contact</a></li>
</ul>

Modern Approach - Flexboxes!

Now that CSS3 flexboxes have better browser support, some of us can finally start using them. Just add additional vendor prefixes for more browser coverage.

In this instance, you would just set the parent element's display to flex and then change the justify-content property to either space-between or space-around in order to add space between or around the children flexbox items.

Using justify-content: space-between - (example here):

ul {
list-style: none;
padding: 0;
margin: 0;
}
.menu {
display: flex;
justify-content: space-between;
}
<ul class="menu">
<li>Item One</li>
<li>Item Two</li>
<li>Item Three Longer</li>
<li>Item Four</li>
</ul>


Using justify-content: space-around - (example here):

ul {
list-style: none;
padding: 0;
margin: 0;
}
.menu {
display: flex;
justify-content: space-around;
}
<ul class="menu">
<li>Item One</li>
<li>Item Two</li>
<li>Item Three Longer</li>
<li>Item Four</li>
</ul>

try this

*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
ul {
list-style: none;
display: flex;
align-items: center;
justify-content: space-evenly;
}
    <ul>
<li>List item One</li>
<li>List item Two</li>
<li>List item Three </li>
<li>List item Four</li>
</ul>