Как я * действительно * оправдываю горизонтальное меню в HTML+CSS?

Вы найдете множество учебных пособий по строкам меню в HTML, но для этого конкретного (хотя IMHO общего) случая я не нашел никакого достойного решения:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • Существует разное количество только текстовых пунктов меню, а макет страницы плавный.
  • Первый пункт меню должен быть выровнен по левому краю, последний пункт меню должен быть выровнен по правому краю.
  • Остальные пункты должны быть оптимально распределены в строке меню.
  • Число варьируется, поэтому нет возможности предварительно рассчитать оптимальную ширину.

Обратите внимание, что ТАБЛИЦА здесь также не работает:

  • Если вы центрируете все TD, первый и последний элементы выровнены неправильно.
  • Если вы выравниваете по левому и по правому краю, то сначала соотв. последние пункты, интервал будет неоптимальным.

Разве не странно, что не существует очевидного способа реализовать это чисто, используя HTML и CSS?

14 ответов

Решение

Современный подход - Flexboxes!

Теперь, когда во флексбоксах CSS3 улучшена поддержка браузеров, некоторые из нас могут наконец начать использовать их. Просто добавьте дополнительные префиксы поставщиков для большего охвата браузера.

В этом случае вы просто установите родительский элемент display в flex а затем изменить justify-content свойство либо space-betweenили жеspace-aroundчтобы добавить пространство между или вокруг дочерних элементов flexbox.

С помощью justify-content: space-between- (пример здесь):

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>


С помощьюjustify-content: space-around- (пример здесь):

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>

Самое простое, что нужно сделать, это заставить линию разрываться, вставляя в конец элемента элемент, который займет больше свободного места, а затем скрывая его. Я сделал это довольно легко с помощью простого span элемент вроде так:

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

Все барахло внутри #menu span Селектор необходим (насколько я нашел), чтобы удовлетворить большинство браузеров. Это должно заставить ширину span элемент на 100%, что должно вызвать разрыв строки, так как он считается встроенным элементом из-за display: inline-block править. inline-block также делает span можно блокировать правила стиля на уровне width что приводит к тому, что элемент не согласуется с меню, и, следовательно, меню разрывается на строки.

Вам, конечно, нужно отрегулировать ширину span к вашему варианту использования и дизайну, но я надеюсь, что вы поймете общую идею и сможете ее адаптировать.

Хорошо, это решение не работает в IE6/7 из-за отсутствия поддержки :before / :after, но:

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>

Причина, по которой у меня есть тег как inline-block потому что я не хочу, чтобы слова внутри тоже были оправданы, и я также не хочу использовать неразрывные пробелы.

Есть решение. Работает в FF, IE6, IE7, Webkit и т. Д.

Убедитесь, что вы не поставили пробелы перед закрытием span.inner, IE6 сломается.

Вы можете по желанию дать .outer ширина

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

Работает с Opera, Firefox, Chrome и 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;
}

Еще одно решение. У меня не было возможности заняться html, например, добавлением выделенного класса и т. Д., Поэтому я нашел чистый способ CSS.

Работает в Chrome, Firefox, Safari... не знаю об IE.

Тест: 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>

Сделай это <p> с text-align: justify?

Обновление: Nevermind. Это не работает вообще, как я думал.

Обновление 2: в настоящее время не работает ни в одном браузере, кроме IE, но CSS3 поддерживает это в виде text-align-last

Текст оправдан только в том случае, если предложение естественным образом вызывает разрыв строки. Таким образом, все, что вам нужно сделать, это, естественно, вызвать разрыв строки и скрыть то, что во второй строке:

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>

Элемент li.break должен находиться в той же строке, что и последний элемент меню, и должен содержать некоторый контент (в данном случае это не пробел), в противном случае в некоторых браузерах, если он не находится на той же строке, вы увидите небольшой лишний пробел в конце вашей строки, и если он не содержит содержимого, он игнорируется и строка не выравнивается.

Протестировано в IE7, IE8, IE9, Chrome, Firefox 4.

Для браузеров на базе Gecko я придумал это решение. Это решение не работает с браузерами WebKit, хотя (например, Chromium, Midori, Epiphany) они по-прежнему показывают завершающий пробел после последнего элемента.

Я поместил строку меню в оправданный абзац. Проблема в том, что последняя строка выровненного абзаца не будет выровнена по понятным причинам. Поэтому я добавляю широкий невидимый элемент (например, img), который гарантирует, что абзац имеет длину не менее двух строк.

Теперь строка меню отображается по тому же алгоритму, который браузер использует для выравнивания простого текста.

Код:

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

Замечание: Вы заметили, что я обманул? Чтобы добавить элемент заполнения пространства, я должен сделать предположение о ширине строки меню. Так что это решение не полностью соответствует правилам.

попробуй это

Если пойти с Javascript, что возможно (этот скрипт основан на 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>

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

и последний 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>

Более простая разметка, протестированная в 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>

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

Живой пример.

Это может быть достигнуто превосходно с помощью некоторых тщательных измерений и выбора последнего ребенка.

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

Я знаю, что исходный вопрос указывал HTML + CSS, но в нем конкретно не говорилось о javascript;)

Пытаясь сохранить CSS и разметку как можно более чистыми и как можно более семантически значимыми (используя UL для меню), я пришел к этому предложению. Вероятно, не идеал, но это может быть хорошей отправной точкой:

<!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>
Другие вопросы по тегам