Slider de imágenes ajustable y con transiciones

Galería, carrusel o slider de imágenes

Una galería, carrusel o slider de imágenes nos permite presentar un conjunto de imágenes. La necesidad de usarlos no es sólo porque resulten atractivos desde el punto de vista del diseño, sino que además pueden arreglar algunos problemas cuando tratamos con un conjunto más o menos grande de imágenes. En un tema anterior slider imágenes no ajustable expuse uno que no ajusta las imágenes al contenedor. El de este tema es un slider de imágenes ajustable al contenedor usando transiciones de CSS3 para conseguir un efecto visual diferente. Las imágenes del conjunto pueden presentarse de forma automática o manual. La dirección en la que aparecen también es configurable, pudiendo ser horizontal o vertical. El avance puede ser también de derecha a izquierda o al revés (arriba-abajo con la dirección vertical). La rotación puede ser circular o en vaivén. Se puede poner un pie de imagen y también un título, es decir, el atributo title. En definitiva una serie de configuraciones que dan bastante juego a este slider de imágenes.

Otra mejora es que usamos AJAX para descargar un archivo de texto que contiene la lista de URL's de las imágenes. La petición se hace no cacheable y logramos tener siempre la última versión de esa lista. Si la configuración es manual sólo se descarga la primera imagen y el resto cuando el usuario lo solicita. En otro caso si es automática, se van descargando según la configuración de tiempo especificada. Las imágenes de la serie se van almacenando en otros tantos elementos <img> que se van agregando al contenedor, por lo que la descarga desde el servidor (o caché) sólo se produce la primera vez en la serie. Esto es una ventaja con respecto al slider del tema anterior donde la descarga siempre se realiza desde el servidor/caché, pues siempre se actualizaba el atributo src del mismo y único elemento <img>.

  • Julio 2017: Sustituidos los sprites de las flechas de desplazamiento por iconos SVG. Ver más sobre iconos SVG.
  • Agosto 2014: He modificado el HTML que carga el slider y también el JS.

El JS original actual está en slider-imagen.js, reflejando la última versión.

Veámos el primer slider de esta página:

<div id="slider1">
<div class="slider-imagen"
data-lista="ejemplos/pasa-fotos/lista.txt"
data-ancho="200" data-alto="200"
data-pie-style="height: 80px; overflow: auto; font: 0.8em 'Arial Narrow'"></div>
</div>
.......
<script>
    window.onload = function(){
        cargarSliderImg();
    }
</script>

Este HTML es el del primer ejemplo de esta página. Con el script tras la carga construimos todos los slider de esta página con cargarSliderImg(). El DIV contenedor exterior no forma parte del slider y su finalidad es para bordearlo y contener el pie de foto. Para evitar el repintado total de la página, le damos un estilo que puede encontrar en la cabecera de este documento:

<style>
    /* Contenedores exteriores de ambos slider imágenes, con dimensiones
       para evitar reflujo y repintado */
    #slider1, #slider2 {
       float: left;
       width: 200px;
       border: gray solid 1px;
       padding: 0.2em;
       margin: 0 0.2em 0.2em 0;
    }
    #slider1 {
        height: 280px;
    }
    #slider2 {
        height: 220px;
    }
</style>

En este estilo configuramos el ancho y alto de los dos slider de esta página. Ambos son de 200x200 y uno tiene un pie de 80px de alto y el segundo de 20px de alto. La necesidad de especificar las dimensiones del contenedor exterior es que en la carga estos DIV no tienen dimensiones. Cuando se cargan los slider estos contenedores se repintan produciendo un reflujo en la página. Para evitarlo les dotamos de dimensiones. Para el alto sumamos la altura del pie de foto.

El slider de imágenes es el <div class="slider-imagen"... que ha de tener necesariamente un atributo data-lista que nos vincula con el archivo de texto que contiene la lista de URL's. Este archivo lista.txt obedece a la siguiente declaración que contiene tres campos y un salto de línea si hay más de una imagen:

(URL1 [ "|" HTML [ "|" URL2 ]] SALTO)+

La primera URL1 es la ruta de la imagen, relativa respecto a la carpeta donde se encuentra ubicada la lista. Las barras verticales se usan para separar los tres campos, por lo que no deben encontrarse en otro sitio de esa declaración. Con esas barras podemos opcionalmente agregar un literal de texto HTML para el pie de imagen. Y también podemos agregar otra URL2 de tal forma que todo el HTML anterior será el contenido de un vínculo, quedando finalmente como <a href="URL2">HTML</a> en el pie de imagen. Por supuesto, hemos de cuidar que la composición HTML final sea conforme, por ejemplo, no podemos anidar un <a> dentro de otro <a>. En la lista podemos tener una o más declaraciones separadas por SALTO de línea. La lista para este ejemplo es así (de forma abreviada):

foto1-225x300.jpg | Foto de <var>225&times;300</var> ...
foto2-225x300.jpg | Otra ...
foto3-225x300.jpg | Esta ...
foto-no-ajusta-384x512.jpg | Foto ...
foto-no-ajusta-512x384.jpg | De ...
foto-ajusta-108x75.jpg | De <var>108&times;75</var> | http...
/res/img/muestra-grafica.png | Esta ...

Las seis primeras tienen las rutas relativas a la carpeta donde está la lista, en ejemplos/pasa-fotos/. Esta ruta a su vez es relativa con respecto a la página, por lo que la absoluta final (relativa respecto al sitio) será /como-se-hace/slider-imagenes/ejemplos/pasa-fotos/. Pero no es necesario que las imágenes estén en la misma carpeta donde esté la lista. La última imagen vemos que está en otra carpeta del servidor. Todas tienen pie de imagen y la penúltima además tiene la segunda URL para formar un vínculo con todo el HTML del pie.

Hay que configurar las dimensiones finales que tomará el slider, en este ejemplo de 200×200 píxeles, declarándolo en los atributos data-ancho y data-alto. Y esto es lo mínimamente indispensable para que funcione. El resto es optativo. En el ejemplo anterior hay también un atributo data-pie-style. Si aparece se dotará de un pie de foto. En ese caso se aplicará el estilo que figure ahí.

Actualización Octubre 2013:

En el window.onload hemos de llamar a cargarSliderImg() que carga todos los slider de una página. Inicialmente se hace una petición con XHR XHR es el acrónimo de XMLHttpRequest. Este término es sinónimo de AJAX, que a su vez viene de Asynchronous JavaScript And XML. La especificación oficial le llama XHR en lugar de AJAX. de un archivo de texto que contiene una lista de rutas y notas al pie de foto separadas por saltos de línea. Cada slider debe tener uno de estos archivos de texto (ver esta muestra lista.txt del segundo ejemplo). La petición XHR de la lista antes era siempre no cacheable. Pero cuando la lista de imágenes no cambia con frecuencia es preferible hacerla cacheable. Además también pudiera ser deseable no hacer peticiones de la lista con XHR cuando incluso la lista de imágenes permanecerá igual de forma indefinida. Siempre que podamos tenemos que evitar peticiones al servidor, especialmente con dispositivos móviles.

Para arreglar ambas cosas ahora la carga se hace con cargarSliderImg(controlLista) donde ese nuevo argumento puede ser ahora:

  • Null o un booleano con valor false: Se carga el archivo de texto con la lista de fotos con XHR cacheable. Los dos slider de esta página se cargan con esta opción cargarSliderImg(), sin especificar el argumento por lo que éste será nulo.
  • Un booleano con valor true: Se carga el archivo de texto con la lista de fotos pero se hace NO cacheable (esta era la situación antes de la actualización).
  • Un array: Este array contiene tantas posiciones como slider haya que cargar en la página. Cada posición será un String con el texto del archivo de cada lista de fotos. Por ejemplo:
    var lista = [
        "foto1.jpg | Pie de foto\n" +
        "foto2.jpg | Pie de foto\n" +
        "foto3.jpg | Pie de foto\n"
        ,
        "img1.jpg | Pie de foto\n" +
        "img2.jpg | Pie de foto\n" +
        "img3.jpg | Pie de foto\n"
    ];
    
    cargarSliderImg(lista);
    Este código cargaría dos listas de fotos para los dos slider de una página.

Configuraciones del slider de imágenes ajustable

Hay varias configuraciones que podemos establecer para el slider de imágenes ajustable. En la izquierda vemos otro ejemplo con este HTML. Tiene el atributo data-direccion = "vertical" que hace que las imágenes transicionen en ese sentido. La rotación se establece con data-rotacion = "vaiven", siendo el otro valor posible "circular". En circular las imágenes pasan como un carrusel sin fin. En vaivén la serie primero va en una dirección y luego en la otra. El avance se refiere a en que sentido de la dirección se inicia la transición. Se declara con data-avance con valor inicial -1. La configuración data-auto" es un valor booleano para indicar si la transición es automática (como este ejemplo) o manual (como el primer ejemplo). Si el atributo aparece se considera true, en otro caso el valor por defecto será false.

Este ejemplo tiene este HTML:

<div id="slider2">
    <div class="slider-imagen"
    data-lista="ejemplos/slider-imagen/lista.txt"
    data-ancho="200" data-alto="200"
    data-direccion="vertical" data-rotacion="vaiven"
    data-avance="-1" data-auto
    data-pie-style="height: 20px; font: 0.8em 'Arial Narrow'"></div>
</div> 

Esto es un resumen de todas las configuraciones. Si el atributo data-* no aparece se tomará el valor por defecto:

Configuracióndata-*Valor por defectoOtros valoresNotas
Automáticodata-autoFalse si no apareceTrue si apareceEs un atributo booleano de HTML. Si aparece la presentación será automática. Si no aparece será manual y se complementa con unos botones para que el usuario solicite una imagen en cualquier lado de la dirección de presentación.
Transicióndata-transicionease-in-outease, ease-in, ease-out, linear, step-start, step-end, steps(n,start|end), cubic-bezier(n1,n2,n3,n4)Se refiere a la propiedad transition-property de CSS3.
Temporización de la transicióndata-tiempo1sSe refiere a la propiedad transition-duration de CSS3. El valor anterior es de 1 segundo.
Intervalo de presentacióndata-intervalo3000En milisegundos, ha de ser mayor que data-tiempo para poder sincronizar adecuadamente el intervalo de transición y este intervalo de espera hasta la presentación de la siguiente imagen de la serie.
Direccióndata-direccionhorizontalverticalLa dirección en la que se aplica la transición. Con horizontal se aplica a la propiedad left y con vertical a top.
Avancedata-avance1-1Si la dirección es horizontal, el avance es de izquierda a derecha con el valor 1 y al revés con el valor -1. Respectivamente para la dirección vertical sería de arriba a abajo con 1 y al revés con -1.
Rotacióndata-rotacioncircularvaivenLa rotación circular se refiere a que las imágenes van pasando en un carrusel sin fin. Con el valor vaivén la serie pasa primero hacia un lado y luego al otro de forma alternativa.
Ajuste de imagendata-dimensionproporcionaloriginal, encajarCon proporcional la imagen se ajusta al contenedor conservando la proporción. Con original no se hace ninguna acción, saltando las barras de scroll si es mayor que el contenedor. Con encajar se escala el tamaño de la imagen para ajustarlo exactamente al contenedor.
Pie de fotodata-pie-styleEstilo CSS (ver nota)Si queremos un pie de foto hay que incluir este atributo. En el valor pondremos estilo CSS adicional o bien al menos un espacio en blanco para que aparezca el pie, algo como data-pie-style=" ". Si no queremos pie no incluiremos este atributo.

El script del slider imágenes con transiciones

Aquí intentaré explicar el funcionamiento del script y otros recursos que son necesarios para que funcione. Hay veces que se explica mejor un script usando un diagrama de flujo. El siguiente es uno resumido para slider-imagen.js, cuyo código puede consultarlo en la cabecera de este documento. Este es un diagrama resumido para mostrar lo principal del funcionamiento, omitiendo algunas cosas como la interacción del usuario cuando el slider está en modo manual. Puede verlo con todo detalle en el diagrama completo. Los círculos de color naranja reprentan las entradas desde y hacia la interfaz de usuario. El color azul representa una ejecución asíncrona. En las bifurcaciones el color verde son salidas afirmativas y rojo las negativas.

Diagrama de flujo algoritmo slider imágenes con transiciones

La principal ventaja de este slider es que incorporamos las URL's de todas las imágenes en un archivo de texto. El script comienza recorriendo todos los elementos HTML de la página con clase slider-imagen. Monta lo necesario para manipularlos en una variable global sliderImg, un array de objetos donde cada uno es un slider. Por cada uno de ellos hace una petición AJAX para traer la lista de URL's. Si ese archivo no está disponible, es decir, si obtenemos un código de estado HTTP (status) distinto de 200, bloqueamos ese slider, aunque seguimos cargando el resto de slider de la página.

Por cada slider llama por primera vez a cambiarSliderImg() para presentar la primera imagen. Mientras se recorre la lista de URL's la primera vez se van creando objetos Image sin aún incorporarlos al DOM. Se rellena su atributo src con la URL y se activan los eventos onload y onerror. Si la carga es satisfactoria se crea un nuevo elemento <img> con el objeto Image anterior y se incorpora al DOM. Si hay un error se utiliza como imagen una que tenemos referenciada en la variable uriNoExiste. Así tenemos siempre la seguridad de que todas las imágenes serán presentadas, pues si la URL no se encunentra usaremos una imagen para avisar que ese vínculo está roto.

Si es la primera imagen no usamos transición, temporizando la ejecución para que en tiempo configurado cambie a la siguiente imagen de la serie. En cambio para la segunda y siguientes imágenes se realiza la transición. Esta es una explicación resumida, pues hay que considerar algunas cosas más cuando no está en modo automático. Esto se puede ver mejor en el diagrama completo que referencié más arriba.

En cuanto al script, hay un par de funciones que tengo ubicadas en el módulo general.js:

Con eso se garantiza el funcionamiento en IE8. Puede copiarlas desde ahí y agregarlas al módulo slider-imagen.js. O bien ignorar a IE8 y usar en el código parentNode y getElementsByClassName() respectivamente.

Otro recurso que se usa para las transiciones CSS3 es el que expliqué en Un gestor vpForCss de vendor prefixes. Por lo tanto es necesario vincular el script vpforcss.js a la página, aunque yo tengo las funciones del gestor de vendor prefixes dentro del módulo general.js. Tendremos que poner lo siguiente en la carga de la página para prefijar la propiedad transition con el vpForCss (función prefijarCss()) antes de cargar los slider de la página:

<script>
    window.onload = function(){
        prefijarCss("transition");
        cargarSliderImg();
    }
</script>
Actualización Octubre 2013:

He eliminado prefijarCss("transition") del window.onload y lo he trasladado al script slider-imagen.js, ejecutándose dentro de la función cargarSliderImg().

Otro recurso que ha de estar disponible es la imagen referenciada en el script en la variable uriNoExiste. Es una imagen que le dirá al usuario que el vínculo está roto:

Vínculo roto

El script no comprueba que esa imagen de vínculo roto exista a su vez. No tendría porque suceder, pero en caso de que no pueda acceder a ella cuando alguna imagen de la lista de URL's no exista, ocurrirá una situación de bloqueo al generarse el evento onerror de forma recursiva. Una forma de evitarlo es incorporar esa imagen en el script en formato de texto codificada en base64, pero incrementaría el tamaño del script. Bueno, es un tema a mejorar.

Ajustando imágenes

No quiero extenderme en los detalles del script, pero si puede ser interesante el algoritmo para ajustar las imágenes al contenedor. Se lleva a cabo en la función loadImg() al ejecutarse el evento onlad en la actualización del atributo src del objeto temporal Image. Por lo tanto el ajuste de la imagen se realiza antes de convertir este objeto en un elemento <img> e incorporarlo al DOM. Consultamos las propiedades width y height del objeto Image obteniendo el ancho original y alto original. Suponiendo que la nueva imagen tiene un ancho_original y un alto_original entonces tenemos el siguiente proceso de ajuste de imagen:

  • Si la configuración es original no hacemos nada, pues la nueva imagen <img> tendrá el ancho_original y alto_original del objeto Image, es decir, de la imagen original recuperada del servidor/caché. Si es más grande aparecerán las barras de desplazamiento.
  • Si la configuración es encajar al contenedor, simplemente le damos el ancho_contenedor y alto_contenedor, valores que inicialmente se almacenaron cuando montamos el slider. La imagen rellena todo el contenedor, aunque se deformará si no son proporcionales.
  • Si la configuración es proporcional ajustamos la imagen al contenedor conservando su proporción. Pueden darse los siguientes casos:
    • Si ancho_original >= alto_original y ancho_original > ancho_contenedor, es decir, si es más ancha que alta (o igual) y el ancho sobrepasa el del contenedor entonces ajustamos al ancho del contenedor y proporcionamos el alto:
      • ancho_imagen = ancho_contenedor
      • alto_imagen = ancho_contenedor * alto_original / ancho_original
    • Si ancho_original < alto_original y alto_original > alto_contenedor, es decir, si es más alta que ancha y el alto sobrepasa el del contenedor entonces ajustamos al alto del contenedor y proporcionamos el ancho:
      • ancho_imagen = alto_contenedor * ancho_original / alto_original
      • alto_imagen = alto_contenedor

Transicionando imágenes con vpForCss

Este slider usa transiciones CSS3 para dar un efecto visual a la aparición de las imágenes. Supongamos como ejemplo para la siguiente exposición que la dirección de presentación es horizontal y que las imágenes van pasando de izquierda a derecha (avance = 1). Supongamos que hay 6 imágenes en la serie y que tenemos la imagen 3 presentada y la rotación es circular. La siguiente imagen será la 4 y previamente a ser presentada estará posicionada a la izquierda del contenedor. Como éste tiene el overflow con valor hidden permanecerá oculta. Por lo tanto hemos de transicionar la 3 hacia la derecha (para ocultarla) y la 4 aparecerá al mismo tiempo por la izquierda. En cada transición se le quita esa propiedad transiton a las imágenes, por lo que lo primero que hacemos es dotarlas:

if (sliderImg[is].rotacion == "circular"){
    sliderImg[is].arrImg[jm].style[vpForCss["transition"]] =
        sliderImg[is].transicion;
    sliderImg[is].arrImg[km].style[vpForCss["transition"]] =
        sliderImg[is].transicion;
}

En el código la variable sliderImg es un array de objetos con todos los slider de la página. Por cada slider hay un array de elementos <img> en arrImg. En cada transición intervienen dos imágenes consecutivas: km es el índice de la imagen que va a transicionar a la derecha y jm la nueva que aparecerá por la izquierda. Veáse que uso vpForCss para dotar la propiedad. En la propiedad transicion del objeto sliderImg ya habíamos guardado la propiedad resumida o shorthand que se usará para la transición. A continuación damos el estilo de posición para ejecutar la transición.

sliderImg[is].arrImg[jm].style[sliderImg[is].transPropiedad] =
    (diferMedidaJ) + "px";
sliderImg[is].arrImg[km].style[sliderImg[is].transPropiedad] =
    (sliderImg[is].avance*(medida+diferMedidaK))

En este caso si la dirección es horizontal ya teníamos el valor left en sliderImg[is].transPropiedad. Por lo tanto la imagen nueva (jm = 4) la posicionamos a la derecha de su posición actual, es decir, parte desde la izquierda del contenedor, oculta, hacia la derecha para ser presentada en el contenedor. A continuación transicionamos la imagen que actualmente ocupa el contenedor (km = 3). Esta se desplazará por la derecha hasta desaparecer. Los valores dados son calculados previamente pues interviene también el hecho de que la imagen fuera menos ancha que el contenedor y tuviera que ser centrada en el mismo.

Pero aún falta una cosa si el movimiento es circular. No podemos dejar las imágenes a la derecha pues cuando la serie se complete volverá a repetirse el movimiento de izquierda a derecha. Entonces también en este momento tendremos que tomar la que estuviera oculta en la derecha (qm = km-1 = 3-1 = 2) en la transición anterior a esta y pasarla a la izquierda del contenedor. Para hacer esto hemos de desactivar la propiedad de transición, pues de otra forma se vería como atraviesa el contenedor hacia la izquierda.

sliderImg[is].arrImg[qm].style[vpForCss["transition"] +
    "Property"] = "none";
....
sliderImg[is].arrImg[qm].style[sliderImg[is].transPropiedad] =
    (-sliderImg[is].avance*medidaQ) + "px"; 

Se aplica vpForCss sólo a transition-property dándole valor none y luego ya si podemos modificar su propiedad left. Así siempre las imágenes estarán disponibles en la izquierda cuando les toque transicionar para ser presentadas. Nuevamente el valor se calcula para que quede ubicada en el sitio correcto teniendo en cuenta si la imagen es menos ancha que el contenedor. También en esos cálculos se incluye el avance, pues al mismo tiempo con ese código se gestiona el movimento de vaivén (no voy a explicarlo para no aburrir más).