Como eu * realmente * justifico um menu horizontal em HTML + CSS?

86

Você encontra muitos tutoriais sobre barras de menu em HTML, mas para este caso específico (embora genérico IMHO), não encontrei nenhuma solução decente:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • Há um número variável de itens de menu somente texto e o layout da página é fluido.
  • O primeiro item de menu deve ser alinhado à esquerda, o último item de menu deve ser alinhado à direita.
  • Os demais itens devem ser distribuídos de forma ideal na barra de menu.
  • O número varia, então não há chance de pré-calcular as larguras ideais.

Observe que uma TABELA não funcionará aqui também:

  • Se você centralizar todos os TDs, o primeiro e o último item não estão alinhados corretamente.
  • Se você alinhar à esquerda e à direita o primeiro resp. nos últimos itens, o espaçamento será abaixo do ideal.

Não é estranho que não haja uma maneira óbvia de implementar isso de forma limpa usando HTML e CSS?

voar
fonte

Respostas:

43

Abordagem moderna - Flexboxes !

Agora que os flexboxes CSS3 têm melhor suporte ao navegador , alguns de nós podem finalmente começar a usá-los. Basta adicionar prefixos de fornecedores adicionais para maior cobertura do navegador .

Nesse caso, você apenas definiria o elemento pai displaycomo flexe, em seguida, alteraria a justify-contentpropriedade para space-betweenou space-aroundpara adicionar espaço entre ou ao redor dos itens filhos do flexbox.

Usandojustify-content: space-between - ( exemplo aqui) :

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>


Usandojustify-content: space-around - (exemplo aqui) :

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>

Josh Crozier
fonte
83

A coisa mais simples a fazer é forçar a quebra da linha inserindo um elemento no final da linha que ocupará mais do que o espaço disponível disponível e, em seguida, ocultando-o. Eu fiz isso facilmente com um spanelemento simples como este:

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

Todo o lixo dentro do #menu spanseletor é (pelo que descobri) necessário para agradar à maioria dos navegadores. Ele deve forçar a largura do spanelemento para 100%, o que deve causar uma quebra de linha, pois é considerado um elemento embutido devido à display: inline-blockregra. inline-blocktambém torna spanpossível bloquear regras de estilo como, por exemplo, widthque faz com que o elemento não se ajuste ao menu e, portanto, o menu quebre.

É claro que você precisa ajustar a largura do spanao seu caso de uso e design, mas espero que você tenha uma ideia geral e possa adaptá-la.

Asbjørn Ulsberg
fonte
1
Parece funcionar se você usar um em spanvez de um hr! Não está funcionando bem, o RH está ocupando espaço visível - use #menu { border: solid 1px green; }para confirmar. Além disso, display: inline-block;não funciona no IE (... 7? CompatibilityView?) Para elementos que não são naturalmente elementos embutidos. HR é um elemento de bloco, então estou supondo que o bloco embutido não funciona para RH no IE. Enfim, span.
ANeves acha que SE é o mal
9
funciona bem, mas os resultados são mais bonitos se você substituir cada espaço por & ensp; (n espaço), de modo que Menu e item e 1 fiquem próximos. & nbsp; não funciona no safari.
yitwail
15
Acabei de passar mais de uma hora batendo minha cabeça contra a parede, tentando descobrir por que isso não estava funcionando para mim. A resposta é que eu precisava de um espaço em branco entre as tags. Estou trabalhando no WordPress e wp_page_menu não inclui quebras de linha após cada <li> </li>. Adicionados usando preg_replace, e funciona agora. Fwewww
Greg Perham
1
Para fazer isso funcionar hoje em dia, você precisa usar display: inline-block nos LIs em navegadores que o suportam e usar display: inline com espaços substituídos por & nbsp; em navegadores que não o suportam. Além disso, como o ie7 não oferece suporte ao bloco embutido, você deve usar embutido em sua tag de quebra automática e apenas inserir o suficiente & nbsp; s nela para forçar a quebra.
Joren
1
Para esclarecer o comentário de Joren, o UL ainda precisa estar em linha e o bloco em linha do LI.
Moss
12

Ok, esta solução não funciona no IE6 / 7, devido à falta de suporte de :before/ :after, mas:

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>

A razão pela qual eu tenho a tag a inline-blocké porque não quero que as palavras dentro sejam justificadas também, e também não quero usar espaços não separáveis.

remitbri
fonte
Obrigado! Sua alma vai direto ao ponto. Se eu fosse iniciar a pergunta, a marcaria como uma resposta.
Andrevinsky
Tive problemas para fazer isso funcionar no IE. Depois de passar algumas horas na terra do Google, finalmente encontrei esta postagem do blog: kristinlbradley.wordpress.com/2011/09/15/… O que finalmente funcionou foi adicionar text-justify: distribute-all-lines;ao ulseletor. Parece ser coisa proprietária do IE.
maryisdead
Não tenho certeza se isso importa, mas usei em width: 100%vez de margin-left: 100%: ul:after{content:""; margin-left:100%;}
Eugene Song
8

Tenho uma solução. Funciona em FF, IE6, IE7, Webkit, etc.

Certifique-se de não colocar nenhum espaço em branco antes de fechar o span.inner. O IE6 será interrompido.

Você pode opcionalmente dar .outeruma largura

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

Micelikespie
fonte
Para mim, esta solução funciona bem com o Gecko, mas não com navegadores WebKit (testado com Chromium, Midori, Epiphany): Com WebKit, há um espaço atrás do último item.
voo de
Certifique-se de que haja apenas um caractere de espaço em branco entre cada extensão e dois entre o último interno e o final. Se isso não funcionar, mexa nos espaços em branco. Existe um bug no webkit.
mikelikespie,
1
.outer {texto-alinhamento: justificar} .outer span.finish {display: bloco embutido; largura: 100%} .outer span.inner {display: inline-block; white-space: nowrap} Não funciona no IE6 e IE7.
Binyamin
Dar as .outer line-height: 0forças a altura da lista a ser renderizada como se consistisse em uma linha.
kontur
4

Funciona com Opera, Firefox, Chrome e 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;
}
Rowinski
fonte
1
Infelizmente, isso não funciona no IE 7 por causa da falta de suporte para células de mesa
Philipp Michael
3

ainda outra solução. Eu não tive nenhuma opção para lidar com o html como adicionar classes distintas etc., então eu encontrei uma maneira pura de css.

Funciona no Chrome, Firefox, Safari ... não sei sobre o IE.

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

bash2day
fonte
2

Fazer <p>com text-align: justify?

Atualização : esquece. Isso não funciona como eu pensava.

Atualização 2 : não funciona em nenhum navegador diferente do IE no momento, mas o CSS3 tem suporte para isso na forma detext-align-last

Jordi Bunster
fonte
Isso foi rápido! Achei que minha solução fosse única, mas você pensou em algo semelhante para começar em apenas alguns instantes.
voo de
Agora em 2016 text-align-last funciona em qualquer navegador diferente do Safari . Este deve ser o possível oponente da abordagem flexbox ou do hack do span .
hakatashi
1

Para navegadores baseados no Gecko, eu vim com esta solução. Esta solução não funciona com navegadores WebKit, embora (por exemplo, Chromium, Midori, Epiphany), eles ainda exibam um espaço à direita após o último item.

Coloquei a barra de menu em um parágrafo justificado . O problema é que a última linha de um parágrafo justificado não será tornada justificada, por razões óbvias. Portanto, adiciono um amplo elemento invisível (por exemplo, um img) que garante que o parágrafo tenha pelo menos duas linhas.

Agora, a barra de menus é justificada pelo mesmo algoritmo que o navegador usa para justificar o texto simples.

Código:

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

Observação: Você percebeu que eu trapaceei? Para adicionar o elemento de preenchimento de espaço, preciso adivinhar a largura da barra de menu. Portanto, esta solução não depende totalmente das regras.

voar
fonte
Observe que você também poderia ter usado uma lista aqui, se definir display: inline nos itens da lista ... as listas são mais convencionais para menus.
Andrew Vit
@Andrew Vit: Você está certo. Isso é principalmente o que a solução do asbjornu também sugere.
voo de
@ voo, se você acha que minha resposta é uma solução, pode marcá-la como uma? No momento, parece que seu problema não foi resolvido. Se não for, seria bom se você nos fornecesse a solução que encontrou ou marque qualquer uma das respostas fornecidas como a solução para o seu problema. :-)
Asbjørn Ulsberg
1

O texto só é justificado se a frase causar naturalmente uma quebra de linha. Portanto, tudo o que você precisa fazer é forçar naturalmente uma quebra de linha e ocultar o que está na segunda linha:

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>

O elemento li.break deve estar na mesma linha que o último item de menu e deve conter algum conteúdo (neste caso, um espaço sem quebra), caso contrário, em alguns navegadores, se não estiver na mesma linha, você verá alguns pequenos espaço extra no final de sua linha e, se não tiver conteúdo, será ignorado e a linha não será justificada.

Testado em IE7, IE8, IE9, Chrome, Firefox 4.

Diaren W
fonte
0

se usar javascript é possível (este script é baseado no 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>

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

e o último 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
fonte
0

Marcação mais simples, testada em 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>

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

Exemplo ao vivo .

Litek
fonte
-1

Isso pode ser alcançado perfeitamente por algumas medições cuidadosas e o seletor do último filho.

ul li {
margin-right:20px;
}
ul li:last-child {
margin-right:0;
}
Jason Paul
fonte
4
Lembre-se de que o texto não renderiza da mesma forma em todos os lugares, um pixel desligado e você veria um menu quebrado. Isso funcionaria apenas com elementos de largura fixa, como imagens.
fregante,
-2

Eu sei que a pergunta original especificava HTML + CSS, mas não dizia especificamente sem javascript ;)

Tentando manter o css e a marcação o mais limpo possível e o mais semanticamente significativo possível (usando um UL para o menu), tive esta sugestão. Provavelmente não é o ideal, mas pode ser um bom ponto de partida:

<!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>
David Heggie
fonte