¿Cómo * realmente * justifico un menú horizontal en HTML + CSS?

86

Encontrará muchos tutoriales sobre barras de menú en HTML, pero para este caso específico (aunque en mi humilde opinión genérico), no he encontrado ninguna solución decente:

#  THE MENU ITEMS    SHOULD BE    JUSTIFIED     JUST AS    PLAIN TEXT     WOULD BE  #
#  ^                                                                             ^  #
  • Hay un número variable de elementos de menú de solo texto y el diseño de la página es fluido.
  • El primer elemento del menú debe estar alineado a la izquierda, el último elemento del menú debe estar alineado a la derecha.
  • Los elementos restantes deben distribuirse de manera óptima en la barra de menú.
  • El número varía, por lo que no hay posibilidad de calcular previamente los anchos óptimos.

Tenga en cuenta que una TABLA no funcionará aquí también:

  • Si centra todos los TD, el primero y el último elemento no están alineados correctamente.
  • Si alinea a la izquierda y alinea a la derecha el primer resp. los últimos elementos, el espaciado será subóptimo.

¿No es extraño que no haya una forma obvia de implementar esto de una manera limpia usando HTML y CSS?

vuelo
fuente

Respuestas:

43

Enfoque moderno - ¡ Flexboxes !

Ahora que los flexboxes CSS3 tienen un mejor soporte para navegadores , algunos de nosotros finalmente podemos comenzar a usarlos. Simplemente agregue prefijos de proveedores adicionales para una mayor cobertura del navegador .

En este caso, simplemente establecería el elemento principal displayen flexy luego cambiaría la justify-contentpropiedad a space-betweeno space-aroundpara agregar espacio entre o alrededor de los elementos secundarios de flexbox.

Usandojustify-content: space-between - ( ejemplo aquí) :

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 - (ejemplo aquí) :

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

Lo más simple es forzar la ruptura de la línea insertando un elemento al final de la línea que ocupará más del espacio disponible a la izquierda y luego ocultándolo. Lo he logrado con bastante facilidad con un spanelemento simple 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>

Toda la basura dentro del #menu spanselector es (por lo que he encontrado) necesaria para complacer a la mayoría de los navegadores. Debería forzar el ancho del spanelemento al 100%, lo que debería provocar un salto de línea ya que se considera un elemento en línea debido a la display: inline-blockregla. inline-blocktambién hace spanposible que las reglas de estilo a nivel de bloque hagan widthque el elemento no encaje en línea con el menú y, por lo tanto, el menú se salte de línea.

Por supuesto, debe ajustar el ancho del spana su caso de uso y diseño, pero espero que obtenga la idea general y pueda adaptarla.

Asbjørn Ulsberg
fuente
1
Parece funcionar si usa un en spanlugar de un hr! Realmente no está funcionando, el HR está ocupando un espacio visible; use #menu { border: solid 1px green; }para confirmar. Además, display: inline-block;no funciona en IE (... 7? CompatibilityView?) Para elementos que no son naturalmente elementos en línea. HR es un elemento de bloque, por lo que supongo que el bloque en línea no funciona para HR en IE. De todos modos, span.
ANeves piensa que SE es malvado
9
funciona muy bien, pero los resultados son más bonitos si reemplaza cada espacio con & ensp; (n espacio), de modo que Menú & elemento & 1 permanezcan juntos. & nbsp; no funciona en safari.
yitwail
15
Pasé más de una hora golpeándome la cabeza contra la pared, tratando de averiguar por qué esto no funcionaba para mí. La respuesta es que necesitaba un espacio en blanco entre las etiquetas. Estoy trabajando en WordPress y wp_page_menu no incluye saltos de línea después de cada <li> </li>. Los agregó usando preg_replace, y funciona ahora. Fwewww
Greg Perham
1
Para que esto funcione en estos días, debe usar display: inline-block en las LI en los navegadores que lo admiten, y usar display: inline con espacios reemplazados por & nbsp; en navegadores que no lo admiten. Además, dado que ie7 no admite inline-block, debe usar inline en su etiqueta force-wrap y simplemente introducir suficientes & nbsp; s en ella para forzar el ajuste.
Joren
1
Para aclarar el comentario de Joren, el UL aún debe estar en línea y el bloque en línea del LI.
Moss
12

Ok, esta solución no funciona en IE6 / 7, debido a la falta de soporte de :before/ :after, pero:

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>

La razón por la que tengo la etiqueta a como una inline-blockes porque no quiero que las palabras del interior también se justifiquen, y tampoco quiero usar espacios que no se rompan.

remitbri
fuente
¡Gracias! Tu alma va al grano. Si tuviera que iniciar la pregunta, la marcaría como una respuesta.
Andrevinsky
Tuve problemas para que esto funcionara en IE. Después de pasar algunas horas en Google Land, finalmente encontré esta publicación de blog: kristinlbradley.wordpress.com/2011/09/15/… Lo que finalmente hizo el truco fue agregar text-justify: distribute-all-lines;al ulselector. Parece ser propiedad de IE.
maryisdead
No estoy seguro de si esto importa, pero usé en width: 100%lugar de margin-left: 100%: ul:after{content:""; margin-left:100%;}
Eugene Song
8

Tengo una solución. Funciona en FF, IE6, IE7, Webkit, etc.

Asegúrese de no dejar espacios en blanco antes de cerrar el archivo span.inner. IE6 se romperá.

Opcionalmente puede dar .outerun ancho

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

mikelikespie
fuente
Para mí, esta solución funciona bien con Gecko, pero no con los navegadores WebKit (probado con Chromium, Midori, Epiphany): con WebKit, hay espacio al final después del último elemento.
vuelo
Asegúrese de que solo haya un carácter de espacio en blanco entre cada tramo y dos entre el último interior y el final. Si eso no funciona, juegue con el espacio en blanco. Hay un error en webkit.
mikelikespie
1
.outer {text-align: justify} .outer span.finish {display: inline-block; ancho: 100%} .outer span.inner {display: inline-block; white-space: nowrap} No funciona en IE6 e IE7.
Binyamin
Dar a las .outer line-height: 0fuerzas la altura de la lista para que se represente como si estuviera formada por una línea.
kontur
4

Funciona con 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
fuente
1
Desafortunadamente, esto no funciona en IE 7 debido a la falta de soporte de celda de tabla
Philipp Michael
3

otra solución más. No tenía ninguna opción para abordar el html como agregar una clase distinguida, etc., así que encontré una forma pura de CSS.

Funciona en Chrome, Firefox, Safari ... no sé sobre IE.

Prueba: 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
fuente
2

¿Hacerlo <p>con text-align: justify?

Actualización : No importa. Eso no funciona en absoluto como pensaba.

Actualización 2 : no funciona en ningún otro navegador que no sea IE en este momento, pero CSS3 tiene soporte para esto en forma detext-align-last

Jordi Bunster
fuente
¡Eso fue rápido! Pensé que mi propia solución era única, pero se te ocurrió algo similar para empezar en solo unos momentos.
vuelo
Ahora, en 2016, text-align-last funciona en cualquier navegador que no sea Safari . Este debería ser el posible oponente del enfoque de flexbox o el hack de span .
hakatashi
1

Para los navegadores basados ​​en Gecko, se me ocurrió esta solución. Esta solución no funciona con los navegadores WebKit, aunque (por ejemplo, Chromium, Midori, Epiphany), todavía muestran un espacio al final del último elemento.

Pongo la barra de menú en un párrafo justificado . El problema es que la última línea de un párrafo justificado no se justificará por razones obvias. Por lo tanto, agrego un elemento invisible ancho (por ejemplo, una imagen) que garantiza que el párrafo tiene al menos dos líneas de largo.

Ahora, la barra de menú está justificada por el mismo algoritmo que usa el navegador para justificar el texto sin formato.

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>

Observación: ¿Notas que hice trampa? Para agregar el elemento de relleno de espacio, tengo que adivinar el ancho de la barra de menú. Por tanto, esta solución no se basa completamente en las reglas.

vuelo
fuente
Tenga en cuenta que también podría haber utilizado una lista aquí, si establece display: inline en los elementos de la lista ... las listas son más convencionales para los menús.
Andrew Vit
@ Andrew Vit: Tienes razón. Eso es principalmente lo que sugiere la solución de asbjornu.
vuelo
@flight, si cree que mi respuesta es una solución, ¿puede marcarla como una? En este momento parece que su problema no está resuelto. Si no es así, sería bueno que nos proporcionara la solución que encontró o marque cualquiera de las respuestas proporcionadas como la solución a su problema. :-)
Asbjørn Ulsberg
1

El texto solo se justifica si la oración provoca naturalmente un salto de línea. Entonces, todo lo que necesita hacer es forzar naturalmente un salto de línea y ocultar lo que está en la segunda línea:

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>

El elemento li. espacio adicional al final de su línea, y si no contiene contenido, entonces se ignora y la línea no está justificada.

Probado en IE7, IE8, IE9, Chrome, Firefox 4.

Diaren W
fuente
0

si ir con javascript es posible (este script se basa en 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>

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

y el ultimo 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
fuente
0

Marcado más simple, probado en 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>

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

Ejemplo vivo .

Litek
fuente
-1

Esto se puede lograr perfectamente mediante algunas medidas cuidadosas y el selector de último hijo.

ul li {
margin-right:20px;
}
ul li:last-child {
margin-right:0;
}
Jason Paul
fuente
4
Tenga en cuenta que el texto no se muestra igual en todas partes, un píxel de distancia y verá un menú roto. Esto solo funcionaría con elementos de ancho fijo como imágenes.
Fregante
-2

Sé que la pregunta original especificaba HTML + CSS, pero no decía específicamente no javascript ;)

Tratando de mantener el CSS y el marcado lo más limpio posible, y lo más semánticamente significativo posible (usando un UL para el menú), se me ocurrió esta sugerencia. Probablemente no sea lo ideal, pero puede ser un buen punto 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
fuente