Vuejs para programadores jQuery. Galeria de imagenes. Componente Vue reusable. XIII

Vuejs para programadores jQuery.

Galeria de imagenes. Componente Vue reusable. XIII


En este post haremos una galería sencilla de imágenes. Para las imágenes hemos usado el servicio de: https://pixabay.com/es que es un servicio gratuito que permite bajar imágenes de gran calidad libres de licencia.


Hemos optado por un creador que tenía imágenes que cuadraban para este proyecto. Su perfil: https://pixabay.com/es/users/valiphotos-1720744/


Empecemos.

La idea general es esta:



  • El paso 1 muestra como esta todo al inicio.
  • El paso 2 es la acción del usuario y lo que sucede con las imágenes (observar que a nivel de código NO movemos la imagen sino el div que la contiene.)
  • El paso 3 pasa cuando la animación ha terminado y no es visible para el usuario. Duplicamos la imagen que se ve en el cuadro del medio.
  • El paso 4 tampoco es visible para el usuario. Hemos hecho un cambio de cuadros y vuelto al paso 1 preparados para el siguiente movimiento. El cuadro que antes se veía ahora se le a agregado una imagen nueva.

Esto mantiene la ilusión de una galería continua con solo 3 imágenes activas. Podría ser infinita si al llegar al final del array de imágenes volviéramos a comenzar desde el inicio.


He intentado hacer el código más sencillo que se me ha ocurrido.


Usamos dos archivos externos: Un js con la lista de imágenes (todas locales en una sub carpeta) y un CSS. Ya hemos dicho un par de veces que estos post no son para tratar CSS, sin embargo este si lo miraremos en algunos puntos, ya que la galería usara animaciones CSS.


JQUERY


Vamos a ello.

Enlaces del JS:

Enlaces del css:

En el CSS el interés se centra en dos keyframes:


@keyframes slidein {

from {

transform: translateX(-100%);

}

to {

transform: translateX(-200%);

}

}



@keyframes slideout {

from {

transform: translateX(-100%);

}

to {

transform: translateX(0%);

}

}


Keyframe, para quien no lo conozca, permite poderosas animaciones css. El uso que aquí le damos es muy básico. Marcamos un estado inicial del elemento HTML y un estado final. slidein y slideout son los nombres que les hemos puesto a estas animaciones.


Como vemos es un movimiento lateral (eje X) del objeto. Al principio de establecer los cuadros de imágenes el que queda en el centro es el que tenemos más a la izquierda y los otros dos se crean a la derecha de este. Por eso los movemos todos al inicio por el eje de las X todos un lugar a la izquierda.


Eso lo definimos aquí:


div.image {

transform: translateX(-100%);

}


Esto nos dará la posición del punto 1 de la explicación gráfica. El valor negativo indica movimiento hacia la izquierda.


Por eso es que los keyframes empiezan en -100% ya que todo los cuadros están en esa posición originalmente con respecto a su sitio al ser creados.


El keyframe slidein mueve aún más a la izquierda los cuadros. Este movimiento afecta en la galería al cuadro central y al cuadro a la derecha (si, si se mueve a la izquierda el cuadro esta a la derecha) al usar el keyframe damos el efecto visual de movimiento y pasamos al paso 2. Los demás pasos se dan inmediatamente después sin intervención del usuario (y si se ha hecho bien sin que se entere)


El keyframe slideout hace el movimiento contrario y por lo tanto afecta al cuadro central y al cuadro a la izquierda.


Los keyframe se aplican usando clases (al poner la clase empieza la animación y si quitamos la clase el cuadro vuelve a su posición o estado original)


Las clases creadas para esto son dos:


.move {

animation-name: slidein;

animation-duration: 1s;

animation-fill-mode: forwards;

}


.retromove {

animation-name: slideout;

animation-duration: 1s;

animation-fill-mode: forwards;

}


Cada una usa una animación diferente identificada en animation-name, tardan un segundo en completar la animación medido en animation-duration y el último campo lo trataremos un poco más.


Cuando se hace una animación con keyframe, por defecto, hace la animación y luego vuelve a su posición original. Se puede modificar ese comportamiento de varias maneras. En este caso con el valor forwards en animation-fill-mode le estamos indicando que al terminar la animación conserve el último estado. Así el movimiento al terminar no vuelve el cuadro a su estado original.


Hay mucho material muy interesante sobre keyframe y es muy recomendable conocerlo si se piensan animaciones de elemento HTML.


Dejo aquí un link donde empezar: https://marksheet.io/css-animations.html

Y un generador online de animaciones CSS: https://cssanimate.com/


Ahora si nos podemos sumergir en el código.

Enlaces:

HTML:

<div class="content-image">

<div class="image" id="imagen1">

<img src="" alt="..." class="image" id="imagen_screen1">

</div>

<div class="image" id="imagen2">

<img src="" alt="..." class="image" id="imagen_screen2">

</div>

<div class="image" id="imagen3">

<img src="" alt="..." class="image" id="imagen_screen3">

</div>

</div>

Esta es la galería en si. Lo primero es decir que los alt DEBERÍAN tener siempre una descripción de las imágenes que den información útil a los dispositivos que no leen imágenes y para las personas con problemas de vista o ciegas. Dejamos mucho de lado la accesibilidad e inconscientemente estamos discriminando a parte activa de la población.


Sigamos. El src en todas está vacío. Cada div y cada imagen tiene un id diferente (en teoría la posición visual es 1 2 3 siendo el 2 el cuadro central antes de cualquier interacción del usuario)


Además tenemos dos botones para los dos movimientos de imágenes posibles.


<div class="grilla sombra">

<button class="btn btn-primary" id="button_left"><<</button>

<div>&nbsp;</div>

<button class="btn btn-primary" id="button_right">>></button>

</div>

Tampoco aquí hay muchas sorpresas.

Vayamos con el JS:


let posicion_actual = 0

let direccion = 0

const image1 = jQuery('#imagen1')

const image2 = jQuery('#imagen2')

const image3 = jQuery('#imagen3')


const screen1 = jQuery('#imagen_screen1')

const screen2 = jQuery('#imagen_screen2')

const screen3 = jQuery('#imagen_screen3')


const button_left = jQuery('#button_left')

const button_der = jQuery('#button_right')

Definimos las constantes que apuntan a los botones, los div que contienen las imágenes y a las imágenes.


La variable posicion_actual apuntará al lugar del array (que tenemos en el archivo externo y se llama listUrls) de la imagen del cuadro central de ese momento.

La variable direccion nos indicará si el último movimiento fue a la derecha o a la izquierda para saber que cuadros modificar y como reordenarlos luego.


image2.on('animationend', animationEnd)

button_der.on('click', mover_der)

button_left.on('click', mover_left)

button_left.attr('disabled', true)

screen1.attr('src', listUrls[posicion_actual])

screen2.attr('src', listUrls[posicion_actual])

screen3.attr('src', listUrls[posicion_actual + 1])


Al que contiene la imagen central (tiene que ser esta porque es la única afectada con los dos movimientos posibles) le asociamos una función al evento animationend que se dispara cuando al animación CSS llego a su fin.


Los botones se asocian a funciones en los eventos clics y deshabilitamos el de la izquierda, ya que en teoría estamos en el primer elemento del array y por lo tanto no hay más elementos para desplazarnos hacia ese lado.


Y al fin damos las urls a los src de las imágenes. Para comenzar solo importan el central y el de la derecha, ya que al estar en el primer elemento no nos podemos mover a la izquierda así que se asigna al de la izquierda y al central el primer elemento del array y al cuadro que esta a la derecha el siguiente (posicion_actual + 1)


Ya lo tenemos montado. Ahora vamos a darle movimiento. Veamos las funciones de los botones


function mover_left() {

direccion = 1

image1.addClass('retromove')

image2.addClass('retromove')

}


function mover_der() {

direccion = 2

image2.addClass('move')

image3.addClass('move')

}


Son super sencillas. Asignan un valor a direccion para saber que movimiento se ha hecho y luego aplican las cases según el botón pulsado. De izquierda a derecha afecta al primer y segundo elemento y de derecha a izquierda afecta al segundo y tercer elemento.


Repito que movemos los div's NO las imágenes.


Y termina ahí el botón. Lo que pase ahora depende del cuadro central y el tiempo que se tarde en terminar la animación que se ha iniciado cuando se asignó la clase.


En ese momento se dispara el evento animationend y se inicia la función:


function animationEnd() {

if (direccion === 1) {

screen2.attr('src', screen1.attr('src'))

setTimeout(()=> {

image1.removeClass('retromove')

image2.removeClass('retromove')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

--posicion_actual

}

if (direccion === 2) {

screen2.attr('src', screen3.attr('src'))

setTimeout(()=> {

image2.removeClass('move')

image3.removeClass('move')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

++posicion_actual

}

if (posicion_actual > 0) {

button_left.removeAttr('disabled')

} else {

button_left.attr('disabled', true)

}

if (posicion_actual < (listUrls.length -1) ) {

button_der.removeAttr('disabled')

} else {

button_der.attr('disabled', true)

}

direccion = 0

}


Parece largo pero lo iremos aplicando según el esquema y veremos que es sencillo en realidad.

Lo primero es comprobar el valor guardado en direccion para entender que debemos hacer. En este momento estamos en el paso 2 de la explicación gráfica.

Estas lineas


screen2.attr('src', screen1.attr('src'))

screen2.attr('src', screen3.attr('src'))


Realizan el paso 3. Se usará una u otra dependiendo de direccion.


Lo que hacemos es copiar el src de la imagen que (temporalmente) está en el centro de la animación (que es la imagen1 o la imagen3) a la imagen2 (temporalmente fuera del centro).


He intentado evitar setTimeout para el siguiente paso pero los eventos de img me han fallado o no he sabido aplicarlos adecuadamente así que tenemos que darle tiempo a <img> para que renderice la imagen que ha cambiado. Si no hacemos esto cuando hagamos el paso 4 habrá un pequeño pero evidente salto visual que afea lo que estamos haciendo.


En realidad no necesitamos mucho tiempo. Con medio segundo es suficiente (y seguramente menos. Invito a cambiarlo y probar)


Para quienes no conozcan setTimeout es una función que permite la ejecución de código después de unos determinados milisegundos (Y hay que recordar eso, los valores que pasamos son en milisegundos)


El formato es sencillo:


setTimeout(function(){}, tiempo)


Veamos los dos usos:


setTimeout(()=> {

image1.removeClass('retromove')

image2.removeClass('retromove')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

--posicion_actual


Aquí estamos haciendo el paso 4. Eliminamos las clases relacionadas con el keyframe por lo que los divs vuelven a su lugar original (El usuario no nota el cambio porque hemos guardado en el cuadro central (que es el que ahora ve) la misma imagen que estaba viendo. Y actualizamos las imágenes de derecha e izquierda con los valores relacionados según la posición actual en el array listUrls.


Ultimo paso, restar uno a posicion_actual.


if (direccion === 2) {

screen2.attr('src', screen3.attr('src'))

setTimeout(()=> {

image2.removeClass('move')

image3.removeClass('move')

screen3.attr('src', listUrls[posicion_actual + 1])

screen1.attr('src', listUrls[posicion_actual - 1] )

}, 500)

++posicion_actual

}


Esta es lo mismo que la anterior pero con los cambios lógicos. Se copia la imagen del otro lado. Se quita la otra clase asociada al keyframe y al igual que antes actualizamos las imágenes de los lados.


Esta vez sumamos uno a posicion_actual


Pero la función no acaba ahí aunque lo que queda es protocolar.


if (posicion_actual > 0) {

button_left.removeAttr('disabled')

} else {

button_left.attr('disabled', true)

}

if (posicion_actual < (listUrls.length -1) ) {

button_der.removeAttr('disabled')

} else {

button_der.attr('disabled', true)

}

direccion = 0


Verificamos que no estamos en el primer elemento del array y si es así desmontamos el botón del desplazamiento hacia la izquierda.

Verificamos que no estamos al final del array y si es así desmontamos el botón del desplazamiento a la derecha.

Ponemos direccion a 0 para evitar repeticiones de acciones.


Y lo tenemos. Ya podemos verlo en Vuejs pero toda la galería la haremos en un componente.


VUEJS


Primero el archivo principal de Vuejs:

Enlaces:

El código es tan pequeño que lo pondremos todo:


<div class="top20 container" id="app">

<gallery :list-content="listUrl"></gallery>

</div>

<script src="links.js"></script>

<script src="galeryComponent.js"></script>

<script>

new Vue({

el: '#app',

data: {

listUrl: listUrls

}

})

</script>


Traemos el componente. Traemos el array externo. Asociamos el array a una variable local y se lo pasamos al componente.

TODO lo demás lo hace el componente. Este es el primer componente 100% reutilizable que creamos en esta serie


Vayamos al componente entonces:

Iremos por partes:

Primero:


const head = document.getElementsByTagName('head')[0]

const style = document.createElement('link')

style.href = 'estilo.css'

style.type = 'text/css'

style.rel = 'stylesheet'

head.append(style)


En vista que la animacion de basa en clases CSS seria un poco hipocrita si el archivo que contiene el componente no fuera capaz de cargar el CSS. Este codigo es para lograr eso y nada mas.


Props ya lo hemos usado y es lo que permite recibir un valor o dato por medio de la marca HTML. Seguimos con el mismo prop que usamos antes, ya que era un array y en este caso también:


props: {

listContent: {

type: Array,

require: true,

}

}


Lo usamos en el HTML cuando hicimos:


:list-content="listUrl"


Ahora veamos un cambio. La definición de las variables en Vue. Hasta ahora usábamos el formato:


data {

variable:valor

}


Pero en los componentes el formato es algo diferente:


data() {

return {

variable: valor

}

}


Unos pocos mas de caracteres. Por lo demás funciona igual. Veamos las variables del componente:


data() {

return {

posicion: 0,

direccion: 0,

imagenes_actuales1:'',

imagenes_actuales2:'',

imagenes_actuales3:'',

retro: false,

move: false,

button_left_desactived: true,

button_right_desactived: false

}

}

posicion y direccion las usaremos con la misma intención que lo hicimos en jQuery. retro y move nos servirán para activar / desactivar las clases correspondientes en los divs contenedores de imágenes.

button_left_desactived / button_right_desactived controlarán el disabled de los botones. Igual que en jQuery comenzamos con el botón izquierdo desactivado.

Hablemos de las string imagenes_actuales. Estas podrían ser un array, sin embargo la reactividad y la actualización de arrays no siempre se llevan bien. Ese tema lo trataremos más adelante pero en vista que solo necesito 3 variables tampoco agobia tener 3 string así que dejo eso para después que aún nos quedan cosas nuevas aquí.

Ahora hablemos de los hook. Vue tiene un ciclo de vida y durante el mismo podemos hacer determinadas cosas según la necesidad. En el gráfico siguiente se puede ver más (los ciclos de vida son los encerrados en rectángulos rojos y escritos en rojo también) y si se desea conocer en profundidad se puede visitar: https://es.vuejs.org/v2/guide/instance.html



En este componente hemos usado uno de ellos: created(). Su nombre es bastante revelador. Se dispara cuando el componente es creado y lo hemos usado para inicializar las strings. Este hook solo se ejecuta una vez en todo el ciclo de vida por lo que no hay posibilidad de sobreescribir los valores.

created(){

this.imagenes_actuales1 = this.listContent[0]

this.imagenes_actuales2 = this.listContent[0]

this.imagenes_actuales3 = this.listContent[1]

}

Hemos inicializado las variables exactamente igual que en jQuery. El primer valor para la imagen1 e imagen2 y el segundo valor para la imagen3 que estará a la derecha.


Antes de pasar al HTML hablemos de los templates dentro de un componente. Hay una regla que no hemos dicho en la creación del componente en el otro post y es que los templates (o sea la parte de HTML) debe tener una marca como padre de todos los demás componentes. Esa marca HTML puede tener tanta descendencia como necesite pero no permite marcas hermanas. Lo explico mejor:


<template>

<div>

....

<div>

</template>


Correcto


<template>

<div>

...

</div>

<div>

...

</div>

</template>


Incorrecto


Como el código que vamos a usar ahora tiene marcas hermanas le hemos agregado a todo un div padre. Si todo esto no quedo claro comentarlo y lo aclararemos. Si da vergüenza comentar me pueden encontrar en el Slack (Y preguntar de forma privada o publica) o en Twitter y preguntar de forma privada o publica.


Veamos el template.


template: `

<div>

<div class="row">

<div class="col-12 grilla">

<div>

&nbsp;

</div>

<div class="content-image">

<div class="image" id="image1" :class="{retromove: retro }">

<img :src="imagenes_actuales1" alt="..." class="image" id="imagen_screen1" >

</div>

<div @animationend="animationEnd" class="image" id="image2" :class="{retromove: retro , move: move }">

<img :src="imagenes_actuales2" alt="..." class="image" id="imagen_screen2" >

</div>

<div class="image" id="image3" :class="{move: move}">

<img :src="imagenes_actuales3" alt="..." class="image" id="imagen_screen3" >

</div>

</div>

<div>

&nbsp;

</div>

</div>

</div>

<div class="row">

<div class="col-12 grilla">

<div>

&nbsp;

</div>

<div class="grilla sombra">

<button class="btn btn-primary" @click="mover_left" :disabled="button_left_desactived"><<</button>

<div>&nbsp;</div>

<button class="btn btn-primary" @click="mover_der" :disabled="button_right_desactived">>></button>

</div>

<div>

&nbsp;

</div>

</div>

</div>

</div>`

RECORDAR: las comillas que usamos no son ni las simples ni las dobles sino de las otras (posiblemente acentos). Veamos las contendores de imágenes. Repetiré el código central que es el más completo:

<div @animationend="animationEnd" class="image" id="image2" :class="{retromove: retro , move: move }">

<img :src="imagenes_actuales2" alt="..." class="image" id="imagen_screen2" >

</div>

Ni una sorpresa. Usamos el mismo evento que en jQuery, las clases dependen de los boolean que vimos en las variables (es la primera vez que mostramos como validar más de una clase) el :src está asociado a la variable y ya lo tenemos.

<button class="btn btn-primary" @click="mover_der" :disabled="button_right_desactived">>></button>

El boton también es predecible. Una función asociada al evento click y el estado disabled según el boolean que explicamos en la parte de las variables.


Veamos los métodos:


Primero los asociados a los botones:


mover_left() {

this.direccion = 1

this.retro = true

}


mover_der() {

this.direccion = 2

this.move=true

}


Damos (como en jQuery) un valor a direccion y ponemos en true la variable correspondiente a la clase según el caso. Ahora solo que la función asociada al final de la animación:


animationEnd() {

const me = this

if (this.direccion === 1) {

this.imagenes_actuales2 = this.imagenes_actuales1

setTimeout(()=> {

me.retro = false

me.imagenes_actuales3= me.listContent[me.posicion + 1]

me.imagenes_actuales1= me.listContent[me.posicion -1 ]

}, 500)

--this.posicion

}

if (this.direccion === 2) {

this.imagenes_actuales2 = this.imagenes_actuales3

setTimeout(()=> {

me.move = false

me.imagenes_actuales3= me.listContent[me.posicion + 1]

me.imagenes_actuales1= me.listContent[me.posicion -1 ]

}, 500)

++this.posicion

}

this.button_left_desactived = this.posicion <= 0;

this.button_right_desactived = this.posicion >= (this.listContent.length - 1);

this.direccion = 0

}


Volvemos con setTimeout pero para tratar otra cosa. Ya hemos hablado de porque lo usamos y como funciona. No obstante en Vuejs nos presenta otra característica. Tiene su propio this. No se puede usar esa palabra reservada para referenciar al objeto de Vuejs. Pero es una dificultad menor, ya que lo que tenemos que hacer es guardar en una constante con otro nombre la referencia al this de Vuejs para poder usar dentro del setTimeout.


Eso es lo que es la primer linea de esta función:


const me = this


Los valores dentro de los if's funcionan igual lo único que manejamos variables. Primero cambiamos (recordemos que aquí estamos pasando del paso 2 al 3 con esta acción) hacia el cuadro2 la imagen que se ve en ese momento. Dentro de setTimeout y usando el me ponemos a false la variable que usamos para colocar las clases del keyframe (lo que lleva al paso final) y actualizamos los cuadros de los lados a la imagen2 (ahora, sin la clase, central)


Sumamos o restamos posicion según el caso.


Actualizar las variables de estado de los botones también es más sencillo. Simplemente es validar el valor actual de posicion y devolver TRUE o FALSE haciendo la condición adecuada a la derecha del signo de asignación (=)


direccion al final le ponemos 0


Listo. Tenemos el componente y lo podemos rehusar en los proyectos que lo necesitemos. Vamos avanzando. Espero que vayamos a un ritmo adecuado. Comentarios bienvenidos.

Enlaces generales:


Consultas, dudas, comentarios: Slack de PEUM o en Twitter.


No hay comentarios:

Publicar un comentario

Vuejs para programadores jQuery. Galería. Load More XVI

Vuejs para programadores jQuery. Galería. Load More XVI En el artículo de hoy vamos a tratar el tema de plugins de jQuery (crearemos dos) ...