Cuando las cosas se desbordan

publicado en la categoría CSS
Una de las características más interesantes del diseño web es que uno no debe preocuparse por conocer previamente la dimensión de los elementos que vamos agregando. Sin embargo, algunas veces, esa ventaja se vuelve un problema si no tenemos en cuenta que si el contenido es más grande que el contenedor puede desbordarse y ocupar espacios indeseados.

Toda etiqueta es un rectangulo que tiene un ancho y un alto; cuando colocamos algo adentro (un texto, una imagen, un video, etc), ese contenedor se ajusta automáticamente ya que las propiedades width y height tienen por defecto el valor auto. Esa es una de las características más interesantes del diseño web, lo que lo hace flexible y hasta sencillo ya que uno no debe preocuparse por conocer previamente la dimensión de los elementos.

Si bien podemos cambiar esas propiedades con cualquier otro valor, si el contenido es más grande que el contenedor, este último se amplia. Esto ocurre porque hay una propiedad llamada overflow que controla esos desbordamientos y cuyo valor por defecto es visible lo que significa que se mostrará el contenido sin importar el tamaño del contenedor.

Esto que parece raro de explicar es lo que ocurre normalmente; por ejemplo, si colocamos un texto dentro de un contenedor al que le damos un determinado ancho, el texto se acomodará y el contenedor tendrá toda la altura necesaria para verlo:

overflow: visible
Aenean fringilla lobortis enim id tempus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros. Vestibulum nec dolor purus, vitae accumsan nunc.

Todo perfecto, la altura se adapta al contenido; sin embargo, cuando esos textos son palabras muy largas, se genera un problema:

overflow: visible

Aenean fringilla. LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros.

Lo primero que uno piensa es bueno, usemos algún otro valor en overflow, por ejemplo hidden hará que ese desbordamiento quede oculto pero claro, el texto se verá cortado:

overflow: hidden
Aenean fringilla. LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros.

Otro valor es scroll; que agregará barras de desplazamiento en ambas direcciones:

overflow: scroll
Aenean fringilla. LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros.

Si usamos el valor auto, como el navegador interpreta que el ancho necesario es insuficiente, sólo agregará una barra de desplazamiento donde sea necesario, en este caso, una horizontal:

overflow: auto
Aenean fringilla. LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros.

Eventualmente, también puede usarse overflow-x u overflow-y que hacen lo mismo pero sólo sobre uno de los ejes, el horizontal o el vertical.

Manejar esos desbordes depende de cada caso en particular aunque a mi entender hay dos situaciones diferentes, una afecta a los textos y otra afecta al resto (imágenes, videos, etc). En estos últimos, para evitar desbordes cuando tenemos contenedores de un ancho fijo, podemos establecer la propiedad overflow:hidden ya que de ese modo impediremos que ciertos objetos se solapen o aparezcan ocupando lugares indebidos; sin embargo, mucho mejor sería definir alguna regla menos drástica para que el contenido fuera fluido y se adaptara automáticamente al contenedor; por ejemplo, algo simple para las imágenes podría ser una regla de este tipo:

img {
	height: auto;
	max-width: 100%;
}

En el caso de los textos, ocultar esos desbordes no servirá porque se cortarán y no podrán leerse. Para resolver esto, existe otra propiedad que debería utilizarse siempre ya que le indica al navegador que, cuando hay un texto demasiado largo, simplemente, lo corte en líneas; esa propiedad es word-wrap y como su valor por defecto es normal debería cambiarse por el valor break-word:

Aenean fringilla. LoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsumLoremipsum, consectetur adipiscing elit. Maecenas ut tortor tellus. Nulla ac elit eros.

Todas esas propiedades son las que nos permiten crear listas largas en pequeños espacios:

  1. black
  2. brown
  3. blue
  4. tomato
  5. green
  6. gray
  7. red
  8. olive

Siempre es bueno tener definida estas propiedades en todos los contenedores cuyo ancho deseamos controlar pero, siempre es demasiado tiempo y a veces, en ciertas condiciones, esto nos impide hacer determinadas cosas. Por ejemplo, una alternativa interesante es que algunos sectores se muestran en alguna clase de etiqueta con barras de desplazamiento pero, cuando colocamos el puntero del ratón encima, el contenedor se expande avanzando hacia la derecha para facilitar su lectura. Para hacer esto, basta usar algún script que quite esa propiedad temporalmente y luego la restaure.

Este es un ejemplo donde se permuta el ancho del contenedor cada vez que hacemos click:

Mauris lorem eros; tincidunt vel elementum a, tempus ac ante! Etiam gravida porttitor convallis?

Sed cursus cursus sem porttitor ullamcorper?

Phasellus congue diam volutpat quam iaculis vel posuere dui sollicitudin!

In et dui quis libero cursus fringilla a non odio. Integer sit amet lorem tortor.

ver/ocultar código ejemplo
#demoexpandir1 {
	cursor: pointer;
	height: auto;
	overflow-x: scroll;
	overflow-y: auto;
	padding: 0 1em;
	text-align: left;
	white-space: nowrap;
	width: 50%;
function demoExpandir1(obj) {
	var estado = obj.style.overflowX; // leemos la propiedad overflowY
	if(estado=="visible") {
		// si está expandido, lo colapsamos
		obj.style.width = "50%"; // restauramos la altura
		obj.style.overflowX = "scroll"; // y agregamos la barra de desplazamiento
	} else {
		// si está colapsado, lo expandimos
		obj.style.width = "100%"; // cambiamos la altura
		obj.style.overflowX = "visible"; // y quitamos la barra de desplazamiento
	}
}
<div id="demoexpandir1" onclick="demoExpandir1(this);"> ... el contenido ... </div>

Animemos un poco ese mismo ejemplo. Pondremos un contenedor al que le daremos un ancho y una altura fija de tal forma que pueda tener cualquier contenido y ocupar siempre el mismo espacio. Usando overflow le adosaremos una barra de desplazamiento vertical para que ese contenido sea accesible pero que no desborde y como detalle extra, pondremos un botón que permitirá expandir o colapsar el contenedor, es decir, le cambiaremos las dos propiedades que controlan ese desbordamiento de modo dinámico.

La idea es que el script funcione en ambos sentidos, si el contenedor está colapsado lo expandirá y si está expandido lo colapsará y para eso necesitamos saber la altura de ese contenido no visible ya que lo hemos cortado.

Hay dos formas básicas, la primera es simplemente cambiar esa altura que desconocemos por height:100% y el navegador se encargará del resto. La segunda es usar scrollHeight que nos dará la altura real aunque no se vea.

En el ejemplo usaremos esta última para poder animar la transición ya que la propiedad transition requiere que el estado inicial uy el final estén expresados en la misma unidad (en este caso pixeles).

Donec blandit tempus congue. Ut tellus nisi, convallis nec sodales a, gravida nec justo. In hac habitasse platea dictumst. Quisque egestas tincidunt augue et dignissim.

Mauris lorem eros; tincidunt vel elementum a, tempus ac ante! Etiam gravida porttitor convallis? Sed cursus cursus sem porttitor ullamcorper? Phasellus congue diam volutpat quam iaculis vel posuere dui sollicitudin! In et dui quis libero cursus fringilla a non odio. Integer sit amet lorem tortor.

Duis mauris libero, fermentum id lacinia sed, molestie sed massa. Etiam ut metus lacus, non fringilla felis. Vestibulum luctus venenatis justo id rutrum. Nullam rhoncus bibendum magna ut consectetur. Praesent suscipit ligula sit amet nibh sagittis nec lobortis mi vulputate. Nulla a turpis quis erat viverra ullamcorper at ac metus. Donec vel neque purus, et suscipit augue. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dignissim laoreet nulla nec eleifend. Quisque vehicula, turpis at dictum volutpat.
ver/ocultar código ejemplo
#demoexpandir2 {
	margin-bottom: 0.5em;
}
#micontenedor {
	height: 200px;
	overflow-x: hidden;
	overflow-y: scroll;
	transition: height 1s ease 0s;
	width: 500px;
}
function demoexpandir2(obj) {
	var elem = document.getElementById("micontenedor");
	// leemos la propiedad overflowY
	if(elem.style.overflowY=="visible"){
		// si está expandido, lo colapsamos
		elem.style.height = "200px"; // restauramos la altura
		elem.style.overflowY = "scroll"; // agregamos la barra de desplazamiento
		obj.innerHTML = "expandir"; // y cambiamos el texto del boton
	} else {
		// si está colapsado, lo expandimos
		elem.style.height = elem.scrollHeight + "px"; // cambiamos la altura
		elem.style.overflowY = "visible"; // quitamos la barra de desplazamiento
		obj.innerHTML = "colapsar"; // y cambiamos el texto del boton
	}
}
<div>
	<button onclick='demoexpandir2(this);' id='demoexpandir2'>expandir</button>
	<div id="micontenedor"> ... el contenido ... </div>
</div>

En muchas ocasiones se quiere algo más sencillo y para eso existe la propiedad text-overflow que nos permite cortar un texto y agregarle un elipse de modo automático. Aunque se supone que es una propiedad relativamente nueva, Microsoft ya la usaba en IE6; en todo caso, la novedad es que ahora funciona en cualquier navegador.

Un elipse no es otra cosa que un carácter especial (tres puntitos) que también podemos escribir de manera manual utilizando entities como &amp;hellip;

Usemos los puntitos: … … … … … … … …

No tiene muchos misterios, es sencilla y se aplica a casi cualquier etiqueta siempre y cuando esta posea algunas características. La propiedad overflow debe tener cualquier valor excepto visible, white-space debe tener el valor nowrap o pre, la etiqueta debe tener un ancho establecido con width y, eventualmente, la propiedad word-wrap debe tener el valor normal.

¿Cómo funciona? Por ejemplo, si colocamos un texto largo en un contenedor con cualquier propiedad gráfica, veríamos algo como esto:

Donec vel neque purus, et suscipit augue. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dignissim laoreet nulla nec eleifend. Quisque vehicula, turpis at dictum volutpat.

Ahora, le daremos un ancho fijo, la propiedad text-overflow y el resto de propiedades necesarias:

.demo {
	overflow: hidden;
	overflow-wrap: normal;
	text-overflow: ellipsis;
	white-space: nowrap;
	width: 400px;
}
Donec vel neque purus, et suscipit augue. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dignissim laoreet nulla nec eleifend. Quisque vehicula, turpis at dictum volutpat.

A partir de ahí, con un poco de JavaScript, podemos crear contenedores que se expandan y contraigan cambiando el atributo class con un click:

Donec vel neque purus, et suscipit augue. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc dignissim laoreet nulla nec eleifend. Quisque vehicula, turpis at dictum volutpat.
ver/ocultar código ejemplo
.demoelipNO, .demoelipSI {
	background: #eee url() no-repeat 0 0;
    border: 1px solid #444;
    padding: 0.5em 0.5em 0.5em 30px;
    width: 400px;
}
.demoelipNO {
    background-image: url(http://i.imgur.com/776izMy.png);
	background-position: 5px .5em;
    overflow: visible;
    white-space: normal;
}
.demoelipSI {
    background-image: url(http://i.imgur.com/GFoyhOq.png);
	background-position: 5px 50%;
    overflow: hidden;
    overflow-wrap: normal;
    text-overflow: ellipsis;
    white-space: nowrap;
}
function elipdemo(obj) {
	if(obj.className=="demoelipSI") {
		obj.className = "demoelipNO";
	} else {
		obj.className = "demoelipSI";
	}
}
<div class="demoelipSI" onclick="elipdemo(this);"> ... el contenido ... </div>