Wextensible

Estructura mínimalista de una página web

Estructura web minimalista

Minimal layout en Wextensible.com
Nueva estructura en un móvil de 320 px de ancho.

El layout o estructura de una página web es la composición de los distintos elementos de la página. A finales del año 2013 modifiqué la estructura anterior de este sitio para incorporar el concepto de estructura mínimalista. La idea es despojar del mayor número de elementos de la estructura que inicialmente se visualiza, pudiendo acceder al resto mediante la acción del usuario. El motivo principal es que tenemos que ajustar nuestras páginas a cualquier ancho de dispositivo, especialmente a los más estrechos.

Ese ajuste lo podemos hacer de muchas formas. Una de ellas es mediante flex-box o cajas flexibles. También podemos usar consultas de medios para ese cometido. En ese tema hice un ejemplo de una estructura de bloques en línea que puede acomodar un lateral según un punto de ruptura declarado en la consulta de medios.

Las estructuras que se modifican con puntos de ruptura son complejas de mantener, especialmente cuando necesitan una modificación importante. Además no me agrada que la vista de la página no sea la misma en cualquier ancho de dispositivo. Para meter una estructura ancha en un dispositivo estrecho hemos de modificar de forma sustancial esa estructura. Como usuario prefiero las páginas que se presentan iguales tanto en un móvil de 49mm de ancho como en un monitor de sobremesa de 19" (~375mm) de ancho.

Específicamente no me gustan laterales con contenidos que saltan al final de la página en dispositivos estrechos. Esto se agrava en los móviles si la página es muy larga y la única forma de acceder al final es usando el scroll. Si un usuario abre una de esas páginas por primera vez en un móvil no sé porque tiene que suponer que en la parte baja hay más contenidos. O si tendrá ganas de ir a ese fondo para comprobarlo.

Menú en un minimal layout
Estructura anterior en este sitio con un lateral derecho y 2 puntos de ruptura en consulta de medios (600px y 800px). Esta vista corresponde a un ancho entre esos dos valores.

Conseguir que una estructura se presente igual en cualquier dispositivo requiere quitarle elementos que colapsan el menor ancho disponible, como los laterales. Pero también tenemos que remodelar la barra de menú descargándola de un exceso de botones. Supongamos que partimos de un mínimo de 285px de ancho para el dispositivo más pequeño. Esto sería el ancho libre que nos queda en un dispositivo pequeño de 320px de ancho tras quitarle algo para rellenos interiores. Diseñaremos la estructura para que quepa en ese ancho y, a la vez, se presente igual en cualquier dispositivo de mayor tamaño.

La imagen adjunta es una captura de la estructura que antes tenía en este sitio. He pasado de una barra con 13 botones/enlaces en la cabecera y 4 en el pie, a la actual que sólo tiene 4 botones arriba y 1 abajo. El resto de enlaces están ahora en un desplegable que se abre con el botón Menú. Ahora no hay laterales de ninguna clase, ni más contenidos en el pie. Los contenidos y enlaces que antes estaban en los laterales ahora los he dispuesto en un columna que se despliega con el botón Accesos.

Estructurando una página web sin laterales

La estructura básica de una página web es ahora más sencilla:

<body>
    <div id="cabeza-menu">
        <div id="cabeza">
            <span>Título</span>
        </div>
        <ul id="menu">
            ...
        </ul>
    </div>
    <div id="contenido">
        ...
    </div>
    <div id="pie">
        ...
    </div>
</body>

En el primer nivel del <body> hay tres zonas: la cabeza-menú, el cuerpo central para el contenido y un pie. En la parte superior de la página encontramos la cabeza para título y otros posibles contenidos como un logotipo. Le sigue una lista <ul> para la barra de menú. Todos estos bloques están identificados con un ID pues serán elementos únicos en la página. El esquema básico anterior finalmente se implementa en este sitio de esta forma:

<!DOCTYPE html>
<html lang="es">
<head>
    ...
</head>
<body>
<header><div id="cabeza-menu"> <div id="cabeza"> <div class="logowx"></div><h1 class="titulowx">TÍTULO DEL SITIO</h1> </div> <nav><h2 class="dispnone">Menú</h2><ul id="menuwx"> ...LISTA DE MENÚ... </ul></nav> </div></header><!-- fin cabeza -->
<div id="contenido"><article> <header><div class="encab-fecha"> <div class="encab-datetime"> <time datetime="AAAA-MM-DD"> <span class="encab-dia">DD</span> <span class="encab-mes">MMM</span> <span class="encab-anyo">AAAA</span> </time> </div> <div class="encab-lista"><button type="button" id="boton-lista-apartados"></button></div> <h1>TÍTULO DE LA PÁGINA</h1> <nav><div id="lista-apartados"> <h4 class="dispnone">Ruta de navegación</h4> <ul> ...LISTA DE MIGAS... </ul> <h4 class="dispnone">Índice de apartados</h4> <ol> ...LISTA DE ENLACES A APARTADOS... </ol> </div></nav> </div></header> <h2 class="num">...</h2> ...CONTENIDO DE LA PÁGINA... </article></div><!-- fin contenido -->
<footer><div id="pie"> <div><a href="#cabeza">Arriba</a></div> </div></footer><!-- fin pie -->
<script>...</script> <script src="/res/inc/general.js" async></script> <noscript id="nojs"><style id="css-after"><?php echo $css_fold["after"]; ?></style></noscript> <noscript><link rel="stylesheet" href="/res/sty/base-nojs.css" /></noscript>
</body> </html>

He resaltado los elementos de marcado semántico de HTML5 <article>, <header>, <nav>, <footer> y <time> que estoy empezando a utilizar con estas últimas actualizaciones. Aunque ahora no voy a profundizar en este complejo tema de la semántica web, si es apropiado decir que a estos no se les aplica ningún tipo de estilo CSS ni tampoco son objeto de referencias desde JavaScript. Se observa que son algo así como envolturas de contenidos, pudiéndose eliminar todos estos tags y la página no sufrirá ningún descalabro. También podemos quitar el primer encabezado <h1> dado que hay un segundo con el título de la página. Otros encabezados <h2> y <h4> tienen class="dispone" que les aplica estilo de display con valor none. No son visibles y sólo se muestran cuando se desactiva el CSS. Todo eso es para intentar dar una estructura semántica y algo de accesibilidad al contenido. Puede ser eliminado porque el documento se sostiene con elementos no semánticos como <div> y <ul>.

La barra de menú de la cabeza se expone con más detalle en un par de ejemplos a continuación. El contenido se distribuye en apartados con encabezados <h2>, todo ello iniciado con una sección <div class="encab-fecha">. Y a su vez envuelto en un header que, como dije antes, puede eliminarse. Ésta sección contiene el título de la página, con una fecha, una lista de vínculos de navegacion a modo de migas y una lista de vínculos a los encabezados <h2> de los apartados de la página. El pie sólo tiene un vínculo para ir a la cabeza. En algunas páginas que forman parte de una serie de temas se agrega además vínculos para ir al anterior y siguiente tema. El resto de vínculos que antes ponía en el pie ahora están en el menú desplegable de la cabeza.

Menú en un minimal layout con interacción sólo CSS

La parte superior de la página es uno de los lugares más importantes desde desde el punto de vista de la interacción del usuario. Es lo que primero se carga y el lugar donde el usuario intentará buscar botones y vínculos con los que interactuar. Ahí debemos poner el título del sitio y a continuación una barra de menú con vínculos, cuadro para un buscador, botones de redes sociales, etc.

Y todo eso son muchas cosas para ponerlas en la primera vista en dispositivos de pequeño tamaño. Lo mejor es ofrecer un conjunto mínimo de menú. He optado por poner los clásicos botones de Inicio y Pie de Página. Podría omitir este enlace al pie pues en esa barra final el único botón será uno que a su vez lleva a la cabeza. Pero es una facilidad para el usuario en cualquier caso. Hay otro botón con la palabra Accesos que abre un lista de accesos directos a temas. Y el botón más importante es el que tiene la palabra Menú para abrir un desplegable con todo el resto de vínculos. También suele usarse el caracter como icono de menú. Pero esta palabra tiene la ventaja de que también es ampliamente conocida y, además, se escribe igual en inglés a excepción de la tilde. En todo caso prefiero usar palabras en esa barra de menú en lugar de iconos.

En este apartado intentaré exponer como realizar una barra de menú con desplegables. En este primer ejemplo usaré sólo CSS para desplegar los menús y en el siguiente usaré JS, que es lo que realmente estoy usando en este sitio. Veámos primero el ejemplo CSS:

Ejemplo: Barra de menú con solo CSS

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

El HTML para este ejemplo es el siguiente:

<div id="body-css">
    <div id="cabeza-menu-css">
        <div id="cabeza-css">
            <span>Súper menú</span>
        </div>
        <ul id="menu-css">
            <li><a href="javascript:alert('Enlace1')">Enlace1</a>
            </li><li id="menu-css-menu"><button type="button">Menú1</button><div
                class="menu-css-desp">
                    <ul>
                        <li><a href="javascript:alert('Enlace')">Enlace3</a></li>
                        ...
                    </ul>
                </div>
            </li><li><a href="javascript:alert('Enlace2')">Enlace2</a></li>
        </ul>
    </div>
    <div id="contenido-css">
        ...
    </div>
    <div id="pie-css">
        <div><a href="#cabeza-css">Arriba</a></div>
        <div></div>
    </div>
</div>

El ejemplo simula que es una estructura para una página. Por eso hay un <div id="body-css"> que hace las veces del elemento BODY de una página. Tenemos los contenedores de la estructura tal como vimos en un apartado anterior. En este ejemplo he agregado -css a los ID para que no entren en conflicto con los existentes en esta página (para el estilo del segundo ejemplo se agrega -js). También he quitado los elementos semánticos. Los enlaces tienen un método de JavaScript para comprobar que funcionan.

El menú que se despliega lo hace en un capa superior para evitar que se desplace la página y al mismo tiempo resulta visualmente más atractivo. Para conseguirlo hemos de posicionar de forma absoluta el contenedor cabeza-menú (este es el mismo estilo para los dos ejemplos -css y -js):

/* Este elemento simula el BODY, con una posición relativa.
*/
#body-css, #body-js {
    position: relative;
    padding: 8px;
    border: blue solid 2px;
}
/* La cabeza-menu contiene una cabeza con el título y otras posibles cosas y otro
contenedor con el menú. Lo posicionamos absoluto para que  el despliegue del menú se
haga en una capa superior al contenido */
#cabeza-menu-css, #cabeza-menu-js {
    position: absolute;
    width: 100%;
    z-index: 99999;
    top:0;
    left:0;
    min-width: 285px;
}
/* El contenido queda en una capa inferior */
#contenido-css, #contenido-js {
    margin-top: 6.5em;
    min-width: 285px;
}

El elemento que simula el <body> tiene posición relativa pues es necesario para posicionar absolutamente alguno de sus hijos. El verdadero <body> tiene inicialmente posición estática, pero un elemento se posiciona absolutamente con respecto al primer padre que tenga posicionamiento relativo. Y si no hubiera ninguno lo haría con respecto al bloque de contención inicial, que precisamente es el <body> de la página. Damos un ancho mínimo al grupo cabeza-menu y también al contenido. Al posicionar absolutamente la cabeza-menu y ponerla en una capa superior el contenido se desplaza hacia arriba, por lo que lo volvemos a bajar dándole un margin-top suficiente.

Y ahora vamos a la barra de menú que tiene tres entradas <li>. El CSS importante que configura la barra según el número de botones y submenús es el siguiente:

/* Hay 3 entradas de menú y por tanto le toca 1/3 de ancho a cada uno */
#menu-css > li {
    display: inline-block;
    text-align: center;
    /* Si son N entradas poner (100/N)% con 5 o 6 decimales */
    width: 33.333333%;
    vertical-align: top;
    }
/* Este es el menú desplegable */
.menu-css-desp {
    display: none;
    /* Si son N entradas será (N*100)%,  */
    width: 300%;
    }
#menu-css-menu .menu-css-desp  {
    /* Este es el segundo y le corresponde -100% */
    margin-left: -100%;
    }
/* El menú de entrada se mostrará con hover. NO podemos usar algo como
#menu-css > li > button:hover + div, #menuwx > li > button:focus + div
porque cuando intentamos entrar en el div el botón pierde el foco y el
div se cierra */
#menu-css > li:hover div {
    display: block;
    }

No se debe dejar espacios entre las entradas <li> de la primera lista para que el acople sea perfecto. Cada entrada puede tener un elemento <a> o bien un <button> que despliega un submenú. A cada <li> que tenga submenú le damos un ancho relativo de (100/N)% siendo N el número de entradas. Para este ejemplo será de 100/3 = 33.333333%. Hemos de poner suficientes decimales para obtener un correcto cálculo. El menú desplegable está dentro de un <div class="menu-css-desp"> y le damos un ancho de (N×100)% que para este ejemplo será 3×100 = 300%. También le damos un margin-left según la posición que ocupe en la lista 0%, -100%, -200% y así de 100 en 100. En este ejemplo el submenú está en la segunda entrada y por tanto tendrá un margin-left: -100%. Todo esto hace que ese <div> que está dentro de un <li> se expanda más allá de su contenedor y ocupe todo el ancho de la lista externa.

Este ejemplo despliega el submenú sólo con CSS cuando hacemos hover sobre el elemento <li>, no sobre el botón de menú. El caso es que estoy incorporando últimamente elementos <button> cuando necesite que un click ejecute alguna acción, como desplegar ese submenú. Podemos usar el propio <li> o un <span>, pero sólo los controles de formulario y los vínculos son capaces de recibir el foco. Mejoramos la accesibilidad si somos capaces de movernos e interactuar por todos los elementos capaces de recibir el foco sólo con la tecla de tabulación, sin usar el ratón.

El problema aquí es que no podemos hacer de forma sencilla que el botón responda a un evento click sólo con CSS. El uso de button:hover + div, o mejor, button:focus + div nos abriría el desplegable, pero esto no nos permitiría hacer click en el contenido de ese submenú puesto que al perder el foco el <div> volvería a cerrarse. Por lo tanto pasamos el hover al elemento <li>. Esta solución con CSS no me parece acertada y necesitamos algo de JS para mejorarlo, como el ejemplo del siguiente apartado.

Menú en un minimal layout con interacción JavaScript

En este ejemplo acompañamos un poco de JS para gestionar la apertura del submenú desplegable. Es una barra de menú con cuatro entradas, dos son enlaces y otros dos son botones que despliegan distintos submenús. Al usar JS podemos interaccionar con la tecla tabuladora para movernos entre las entradas. Cuando el botón de submenú tenga el foco podemos interaccionar con la tecla enter para abrir y cerrar el submenú. Y por supuesto, también con el ratón. Este comportamiento mejora la accesibilidad.

Ejemplo: Barra de menú con JS

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Las únicas diferencias con el ejemplo anterior son las del dimensionamento de la barra de menú y que no usaremos CSS para desplegar y ocultar los submenús:

/* Hay 3 entradas de menú y por tanto le toca 1/3 de ancho a cada uno */
#menu-js > li {
    ...
    /* 100/4 = 25% */
    width: 25%;
    }
/* Este es el menú desplegable */
.menu-js-desp {
    display: none;
    /* 4x100= 400%,  */
    width: 400%;
    }
#menu-js-menu1 .menu-js-desp  {
    /* Este es el segundo y le corresponde -100% */
    margin-left: -100%;
    }
#menu-js-menu2 .menu-js-desp  {
    /* Este es el cuarto y le corresponde -300% */
    margin-left: -300%;
    }
/* En este ejemplo se activará con JS
#menu-js > li:hover div {
    display: block;
    }
*/      

El JavaScript para mostrar y ocultar los submenús es el siguiente:

<script>
    var Wextensible = Wextensible || {}, wxG;
    Wextensible.local = {};
    var wxL = Wextensible.local;

    window.onload = function(){
        wxG = Wextensible.general;
        //Adjudica eventos para el ejemplo de barra de menú con JS
        var menuJsMenu1 = document.getElementById("menu-js-menu1");
        var menuJsMenu2 = document.getElementById("menu-js-menu2");
        var contenidoJs = document.getElementById("contenido-js");
        wxG.agregarEventListener(null, [[menuJsMenu1, "outer"], [menuJsMenu2, "outer"],
            [contenidoJs, "outer"]]);
        menuJsMenu1.addEventListener("click", wxL.mostrarMenu, false);
        menuJsMenu2.addEventListener("click", wxL.mostrarMenu, false);
        contenidoJs.addEventListener("click", wxL.mostrarMenu, false);
    };

    //Manejador del evento CLICK del segundo ejemplo de menús desplegables
    //que se accionan con JS.
    wxL.mostrarMenu = function(event){
        var evt = event || window.event;
        var button = evt.target || evt.srcElement;
        var esteMenu;
        if (button.tagName.toLowerCase() == "button"){
            esteMenu = button.nextSibling;
        }
        var menusDesp = wxG.arrayClassName("menu-js-desp");
        for (var i=0, maxi=menusDesp.length; i<maxi; i++){
            if (menusDesp[i] == esteMenu){
                if (esteMenu.style.display != "block") {
                    esteMenu.style.display = "block";
                } else {
                    esteMenu.style.display = "none";
                }
            } else {
                menusDesp[i].style.display = "none";
            }
        }
    };
</script>    

Con el window.onload adjudicamos eventos para los botones y también para cerrar los submenús cuando hagamos click en el contenido. El manejador del evento click de los botones busca el <div> hermano que es el que contiene el submenú. Iteramos por todos los submenús para cerrarlos si estuvieran abiertos y dejamos abierto (o cerrado) el que se está activando.

Para que funcione en navegadores que no soportan addEventListener (IE8) es necesaria la llamada a wxG.agregarEventListener(), línea de código que puede eliminarse si no desea ese soporte. También para el soporte de document.getElementsByClassName() que se suple con wxG.arrayClassName() (puede cambiarla por la otra si no desea ese soporte). Ambás funciones se encuentran en el módulo general.js.