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


Encuentras muchos tutoriales en barras de menú en HTML, pero para este caso específico (aunque genérico en mi humilde opinión), 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 repartirse de manera óptima en la barra de menús.
  • El número está variando, por lo que no hay posibilidad de pre-calcular el anchos óptimos.

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

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

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

 84
Author: Paul D. Waite, 2008-09-08

13 answers

Enfoque moderno - Flexboxes !

Ahora que CSS3 flexboxes tienen mejor soporte para navegadores, algunos de nosotros finalmente podemos comenzar a usarlos. Simplemente agregue prefijos de proveedor adicionales para más cobertura del navegador.

En este caso, simplemente establecería el elemento padre display a flex y luego cambiaría justify-content propiedad a cualquiera space-between o space-around para agregar espacio entre o alrededor de los niños flexbox elemento.

El Uso de justify-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>

El Uso de justify-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>
 41
Author: Josh Crozier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-26 02:27:24

Lo más simple es forzar la línea a romperse insertando un elemento al final de la línea que ocupará más que el espacio disponible izquierdo y luego ocultándolo. He logrado esto muy fácilmente con un simple span elemento así:

#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 selector #menu span es (por lo que he encontrado) necesaria para complacer a la mayoría de los navegadores. Debe forzar el ancho del elemento span al 100%, lo que debe causar un salto de línea ya que se considera un elemento inline debido a la regla display: inline-block. inline-block también hace que span sea posible bloquear reglas de estilo como width, lo que hace que el elemento no encaje en línea con el menú y, por lo tanto, el menú se rompa de línea.

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

 83
Author: Asbjørn Ulsberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-03 18:47:10

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 inline-block es porque no quiero que las palabras dentro también se justifiquen, y tampoco quiero usar espacios que no rompan.

 12
Author: remitbri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-03 18:48:22

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

Asegúrese de no poner ningún espacio en blanco antes de cerrar el span.inner. IE6 se romperá.

Usted puede opcionalmente dar .outer un 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>
 8
Author: mikelikespie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-03 18:50:25

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;
}
 4
Author: Szajba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-30 15:15:18

Otra solución. No tenía opción para abordar el html como agregar clase distinguida, etc., así que encontré una forma css pura.

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>
 3
Author: bash2day,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-03 18:52:55

Que sea un <p> con text-align: justify?

Update : Nevermind. Eso no funciona como pensaba.

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

 2
Author: Jordi Bunster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-08 12:14:43

Para los navegadores basados en Gecko, se me ocurrió esta solución. Esta solución no funciona con navegadores WebKit, aunque (por ejemplo, Chromium, Midori, Epiphany), todavía muestran espacio final después 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 verá justificada, por razones obvias. Por lo tanto, agrego un elemento invisible amplio (por ejemplo, un img) que garantiza que el párrafo tenga al menos dos líneas largo.

Ahora la barra de menú está justificada por el mismo algoritmo que el navegador utiliza 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: ¿Te das cuenta de que hice trampa? Para agregar el elemento de relleno de espacio, tengo que hacer algunas conjeturas sobre el ancho de la barra de menú. Así que esta solución no depende completamente de las reglas.

 1
Author: flight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-07 11:02:12

El texto solo se justifica si la oración causa naturalmente un salto de línea. Así que 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. break debe estar en la misma línea que el último elemento del menú y debe contener algún contenido (en este caso un espacio no roto), de lo contrario, en algunos navegadores, si no está en la misma línea, verá un pequeño espacio adicional al final de su línea, y si no contiene contenido, entonces es ignorado y la línea no está justificada.

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

 1
Author: Diaren W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-16 00:38:14

Si ir con javascript que es posible (este script está basado 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 ú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>
 0
Author: Raksmey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-27 06:47:33

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

 0
Author: Litek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-16 00:35:11

Esto se puede lograr perfectamente con algunas mediciones cuidadosas y el selector de último hijo.

ul li {
margin-right:20px;
}
ul li:last-child {
margin-right:0;
}
 -1
Author: Jason Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-19 05:05:20

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 es 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>
 -2
Author: David Heggie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-08 13:05:15