La moda de las modales

publicado en la categoría JavaScript
Una ventana modal no es otra cosa que un contenedor HTML como cualquier otro que tiene una característica básica, permanecer oculta hasta que se hace click en algún botón. En principio, nada en particular y podrían diseñarse cientos de modelos gráficos pero digamos que lo más usual es que al abrirse, la pantalla se oscurece y sobre la página se muestra un elemento con cierto contenido que luego, podemos cerrar.

Una ventana modal no es algo tan sofisticado como se cree. Si bien es cierto que hay decenas de librerías que permiten crearlas, si no queremos nada excesivamente complejo ni con muchas opciones, crear una propia no es cosa de magia negra, sólo hay que entender un par de conceptos elementales y probar a ver qué sale.

Cuando hablamos de ventanas modales no debemos confundirnos con las ventanas de tipo pop-up que se generan con JavaScript y que no son otra cosa que una instancia del mismo navegador al que abrimos en una ventana nueva con cierta dimensión y en cierta posición usando window.open(). Una ventana modal no es igual, es ... un rectángulo que contiene cosas, un contenedor HTML como cualquier otro que tiene una característica básica, permanecer oculta hasta que se la necesita.

¿Y que la hace distinta a tantos otros elementos que permutan su visibilidad al hacer click en algun botón? En principio, nada en particular y podrían diseñarse cientos de modelos gráficos pero digamos que lo más usual es que al abrirse, la pantalla se oscurece y sobre la página se muestra un elemento con cierto contenido que luego, podemos cerrar.

¿Cómo se logra esto? Lo más sencillo es usar dos contenedores, un dentro del otro; el exterior será semitransparente y el interior, con su contenido y ambos permanecen ocultos hasta que hacemos click en alguna parte. Esos contenedores conviene colocarlos al final de la página aunque podrían estar en cualquier otro sitio o incluso, podrían ser agregados onfly con alguna función de JavaScript.

De manera genérica, este sería el esquema de un código HTML y su estilo:

<style>
	#overlay-modal {
		background-color: rgba(0, 0, 0, 0.8);
		display: none;
		height: 100%;
		left: 0;
		position: fixed;
		top: 0;
		width: 100%;
		z-index: 10000;
	}
	#contenedor-modal {
		background-color: #fff;
		left: 50%;
		padding:1em;
		position: fixed;
		top: 50%;
		transform: translateX(-50%) translateY(-50%);
	}
	#contenido-modal {
		background-color: #fff;
		height: 100%;
		padding: 1em;
		width: 100%;
	}
	#cerrar-modal {
		cursor: pointer;
		background-color: #fff;
		border-bottom: 1px solid #333;
		border-radius: .5em;
		display: inline-block;
		height: 1.5em;
		padding: 0 .5em;
		position: absolute;
		right: 0;
		top: -1.5em;
	}
</style>

<div id="overlay-modal">
	<div id="contenedor-modal">
		<div id="contenido-modal">
			<!-- el contenido -->
		</div>
		<div id="cerrar-modal" onclick="cerrarModal();">cerrar</div>
	</div>
</div>

<script>
	function abrirModal(){
		document.getElementById("overlay-modal").style.display = "block";
	}
	function cerrarModal(){
		document.getElementById("overlay-modal").style.display = "none";
	}
	</script>

Lo que hace que la ventana modal sea más grande o más pequeña es su contenido y una de sus características más comunes es que se muestran en el centro mismo de la página, sea cual sea el navegador que se usa y sea cual sea su tamaño o dónde nos encontremos dentro de ella; esto es sencillo si utilizamos CSS.

La ventana la mostramos utilizando cualquier enlace o botón con un evento onclick que ejecute alguna función pero necesitamos definir su contenido y ahí es donde nos chocamos con la realidad.

Si el contenido es algo fijo no hay problema, colocamos el HTML en el contenedor y listo. Si el contenido es variable habrá que buscar la forma de crear las funciones correspondientes. Por ejemplo, supongamos que queremos usarla para mostrar imágenes que en la página se ven como miniaturas. Podríamos definir que una etiqueta cualquiera, por ejemplo span se transforme en una etiqueta más inteligente, asociando todas las que tengan cierta clase con un nuevo evento onclick (abrir la modal con la imagen completa).

window.onload = function() {
	// creamos una lista con todas las etiquetas span
	var s = document.getElementsByTagName("span");
	// y las leemos una por una
	for (var i=0; i<s.length; i++) {
		var c = s[i].className; // el contenido del atributo class
		// si es la etiqueta tiene esa clase le agregamos un evento
		if (c=="imgmodal") {
			s[i].addEventListener("click", DEMOimgMODAL, false);
		}
	}
}

Cuando se termina de cargar la página, buscamos todas las etiquetas span con la clase imgmodal y les adosamos un evento que es el que se ejecutará. Esta es la función que se ejecutará cada vez que se haga click en esas etiquetas. Por supuesto, hay decenas de métodos para que la función sepa que hacer pero, en este caso, simplemente colocaremos la dirección url de la imagen completa en un atributo cualquiera:

<span class="imgmodal" url="imagen_original"><img src="imagen_miniatura"></span>
Así que sólo leeremos esa dirección para crear el contenido de la modal antes de mostrarla:

function DEMOimgMODAL(){
	var laURL = this.getAttribute("url");
	document.getElementById("contenido-modal").innerHTML = "<img src='"+laURL+"'>";
	document.getElementById("overlay-modal").style.display = "block";
}

Si se trata de imágenes externas que pueden demorar en ser cargadas, quizás convenga esperar que ocurra eso antes de mostrarlas y para eso usamos una función genérica intermedia que cargará la imagen en la memoria, esperará hasta que esté completa y recién entonces enviará los datos y mostrará la modal.

Mejoremos entonces las funciones del ejemplo anterior:

// función genérica que espera que una imagen se haya cargado
function esperarIMAGEN(src, callback) {
	var imagen = new Image();
	imagen.src = src;
	if (imagen.complete) {
		callback(imagen); // listo ya tenemos los datos
		imagen.onload = function() {};
	} else {
		imagen.onload = function() {
			callback(imagen); // listo ya tenemos los datos
			imagen.onload = function() {};
		}
		imagen.onerror = function() {
			// ante cualquier error no hacemos nada
			alert("error al cargar la imagen");
		}
	}
}

// la función que ejecuta el evento onclick
function DEMOimgMODAL(){
	var laURL = this.getAttribute("url");
	esperarIMAGEN(laURL,mostrarIMAGEN);
}

// la función que muestra la ventana modal
function mostrarIMAGEN(imagen){
	document.getElementById("contenido-modal").innerHTML = "<img src='"+imagen.src+"'>";
	document.getElementById("overlay-modal").style.display = "block";
}

A partir de eso, cualquier variante es posible si se entiende como funciona ya que sólo hay dos puntos a definir, cuales son los atributos donde agregaremos los datos a usar y cómo escribiremos la salida. Por ejemplo, hacemos lo mismo pero con un vídeo de YouTube. Definimos que el atributo a usar se llamará yt y contendrá el identificador del vídeo.

<span class="vidmodal" yt="12345678901">ver video</span>

Usaremos la misma modal así que sólo agregaremos un evento más:

if (c=="vidmodal"){s[i].addEventListener("click", DEMOvidMODAL, false);}

Y en la función sólo sería necesario insertar la etiqueta iframe que sería siempre la misma y donde sólo cambiaría el ID del video:

function DEMOvidMODAL(){
	var idVID = this.getAttribute("yt");
	var tag = "<iframe width='853' height='480' src='https://www.youtube.com/embed/"+idVID+"?autoplay=1' allowfullscreen></iframe>";
	document.getElementById("contenido-modal").innerHTML = tag;
	document.getElementById("overlay-modal").style.display = "block";
}
ver video 1 ver video 2 ver video 3 ver video 4

Me parece elemental entender el por qué de las cosas, es un vicio que vale la pena cultivar porque a la larga, simplifica la vida y en el caso de las ventanas modales hará que el resultado final sea un código muy corto y sin cosas raras; algo que uno pueda entender y adaptar a sus propias necesidades sin tener que seguir un curso avanzado en la NASA pero, aún así debemos tener en cuenta que las ventanas modales perfectas no existen, todas tienen algo que no pueden hacer o limitaciones de alguna clase y no hay manera de abarcar todas las posibilidades ya que son infinitas; es por eso que lo primero a definir es establecer para qué las necesitamos y cuál es la forma más cómoda de usarlas. El código y sus funciones son la forma de obtener eso y no lo inverso.

cerrar