soy Kseso y esto EsCSS

Troceado de imagen con javascript y manejo de cada parte

Troceado de imagen con javascript y manejo de cada parte

✎ 2
COLABORACIÓN AUTOR INVITADO
mosaico

Retomamos los efectos con imágenes. Aunque no sean más que una excusa para ensayar métodos y técnicas que alguna vez nos van a servir para proyectos más útiles.

Hace un tiempo hablamos en la comunidad Puro CSS sólo demos de las máscaras aplicadas sobre elementos simulando "cortes" o "calados" para ver el fondo (12. Text mask); y una versión que parece cortar imágenes en partes circulares podría ser ésta:

See the Pen Decomposed image in circles. (Simulation with opaque masks.) by solipsistaCP (@solipsistacp) on CodePen.

Aquí tenemos un primer ejemplo con una máscara sobre una img y otro con una hecha en el background-image del figure contenedor.

Pero al cambiar el BGColor del documento ya le vemos los piolines. El color negro solamente "parece" ser el fondo, así que si tenemos un diseño detrás, el método no sirve.

Más tarde publiqué una versión que sí transparenta una parte del elemento usando CSS blend ( Imagen con degradado...) que aún no funciona en los navegadores de Microsoft. Un equivalente a la demo anterior sería:

See the Pen Decomposed image in circles. (Simulation with blend masks.) by solipsistaCP (@solipsistacp) on CodePen.

Pero sigue teniendo una limitación enorme : no se puede manipular cada parte de la imagen por separado.

Simulando un 'split image' al vuelo.

Usando lenguajes de servidor sí se puede segmentar una imagen como para reconstruírla en el navegador y darle a cada parte una animación, un enlace o un formato distintos. Pero si vamos a usar javascript (sin canvas) todo seguirá siendo una simulación. Aunque bastante creíble.

See the Pen Rebuild picture with effect "sectorized rotation". by solipsistaCP (@solipsistacp) on CodePen.

La imagen se muestra como un puzzle mal ordenado, y con un click se rearma girando las piezas.

En realidad, lo que hacemos con la original es eliminarla de su contenedor y reemplazarla con una serie de div's que cubren completamente el figure, y en cada uno se pone como fondo la misma imagen, pero desplazada para que coincida la parte visible con la que debería tener la imagen completa en ese lugar. Luego se la rota con CSS para que parezca "desarmada" y como ya tenemos un elemento para cada pieza del rompecabezas, les damos unas animaciones de redondeo y de giro hasta que queden en su lugar.

El código está dividido en dos funciones

onload = inicia; //EMPIEZA EL CAMBIO AL CARGAR TODO EL DOCUMENTO var elContenedor, laImagen; //VARIABLES GLOBALES A USAR /* CALCULA LAS MEDIDAS Y ARMA LA ESTRUCTURA QUE REEMPLAZA LA IMAGEN */ function inicia() { /* REFIERE AL CONTENDOR figure */ elContenedor = document.querySelector("figure"); /* REFIERE A LA RUTA DE LA IMAGEN */ laImagen = elContenedor.querySelector("img").src; /* PONE IMAGEN COMO FONDO DE CONTENEDOR */ elContenedor.style.backgroundImage = "url("+laImagen+")"; /* VACÍA EL CONTENEDOR */ elContenedor.innerHTML = ""; /* AGREGA EL EVENTO AL CONTENEDOR */ elContenedor.addEventListener("click", giro, false); /* CALCULA CUANTOS DIV'S DE 30px CABEN POR FILA Y COLUMNA */ totalDivsCol = 360/30; totalDivsFil = 480/30; /* POR CADA LÍNEA HORIZONTAL ... */ for(f=0; f<totalDivsFil; f++) { /* ... CREA TANTOS DIV'S COMO HAYA EN VERTICAL ... */ for(c=0; c<totalDivsCol; c++) { pieza = document.createElement('div'); /* ... Y APROVECHA SUS VARIABLES PARA POSICIONAR Y ROTAR EL FONDO */ pieza.style.backgroundPosition = (c*-30)+"px "+(f*-30)+"px"; pieza.style.transform = "rotate("+ (90*f*c)+270 +"deg)"; /* LUEGO LO AGREGA AL CONTENEDOR */ elContenedor.appendChild(pieza); } } } /* ANIMACIÓN DE LOS CUADROS */ function giro() { /* REFIERE AL LIST-ITEM DE CUADROS */ var discos = elContenedor.querySelectorAll("div"); /* CAMBIA EL TITLE DE FIGURE */ elContenedor.title = "Gabriela Vargas"; /* RECORRE TODOS LOS DIV'S, REDONDEA Y ROTA A POSICIÓN ORIGINAL */ for(c=0; c<(totalDivsCol*totalDivsFil); c++) { discos[c].style.transform = "rotate(0)"; discos[c].style.borderRadius = "100%"; } /* ESPERA UN TIEMPO A TERMINAR LA ROTACIÓN ANIMADA */ setTimeout(function(){ /* RECORRE LOS DIV'S PARA QUITAR EL REDONDEO DE ESQUINAS */ for(c=0; c<(totalDivsCol*totalDivsFil); c++) { discos[c].style.borderRadius = "0"; } }, 6000); }

No hay muchas novedades en cuanto a javascript, por eso hacemos una descripción breve. Sugiero practicar con el método de tabulado (los div's creados por fila y por columna son justamnete eso) porque en futuros proyectos seguro los vamos a usar.

  • Empezamos con el evento que inicia el escript para reescribir el contenido. Y declaramos dos variables globales.
  • La función de inicio ubica el objeto contenedor y lo referencia junto con la ruta de la imagen en sendas variables. Así la pone como su fondo y ya puede eliminar la etiqueta img vaciando el contenedor. También le agrega el evento para el click que dispara la segunda función.
  • Como previamente elegimos el valor de 30 pixeles de lado para cada cuadrito (30 es submúltiplo de 360 y de 480, así que da una cantidad exacta), entonces averiguamos cuántos caben por fila y por columna con una simple división, y guardamos los valores en variables.
  • Ahora vemos la parte más crítica. Hay muchas formas de crear elementos y aplicar formatos en secuencia, y vamos a usar para cada caso la que nos resulte más práctica. Según el CSS podríamos ir tirando div's en el contenedor que se van a acomodar solos por su float:left, pero resulta que la posición del background depende de la posición de su cuadro, así que vamos a aprovechar el bucle (o los bucles) y sabiendo cuantos caben por fila y cuantos por columna y a qué distancia está el inicio de uno con respecto al otro (sí, son cuadrados, están a 30 pixeles) entonces ni precisamos leer los valores del documento. Vamos a calcular todo con esos datos, para eso anidamos un bucle con la cantidad de filas dentro de uno con la cantidad de columnas, y a cada variable incremental la llamamos y la multiplicamos por -30 para saber la distancia de cada cuadro en horizontal y vertical, y esas serán las coordenadas del fondo (30 por el lado del cuadro y el negativo porque el fondo precisa un valor negativo para ubicarse). Si estamos en el cuadro de arriba a la izquierda (0:0) la imagen de fondo estará en 0px 0px porque 0×-30=0; pero si vamos al segundo de arriba en la siguiente vuelta ya será -30:0; porque va a seguir en el loop de la segunda coordenada mientras avanza el anidado de la primera, y será 1×-30=-30 : 0×-30=0; y la imagen de fondo va a quedar en -30px 0px. Cuando termina con toda esa serie, el primer bucle incrementa la variable, y entonces empieza de nuevo pero con la siguiente fila.
  • Junto con las coordenadas del fondo agregamos una transformación para rotar en "z" el cuadro, pero de a pasos de 90° o múltiplos, como para asegurarnos de que siempre se van a ver "cuadrados" y no en "rombo", o más torcido. Podríamos usar Math.random(), pero ya tenemos números que cambian a cada vuelta, y van aumentando a medida que avanzan los bucles. Así el primero es (90×0×0)+270=270° y el último (90×15×11)+270=15120° . Cualquier combinación que hagamos está bien, va en gustos y dependen mucho del diseño de la imagen. Una vez terminada cada sub-vuelta de bucle, ponemos el div formateado en el final del contenedor como un nuevo hijo.
  • La función que gira los cuadros primero cambia el title del contenedor y luego simplemente los recorre de nuevo, ya sin tanta complicación, del primero al último del list-item. Porque lo único que va a cambiar es el valor de los grados que serán 0° para todos (y CSS se encarga de hacerlos girar hasta ahí) y del redondeo de las esquinas a 100% (también tiene transition). Lo más improvisado es un setTimeout() que vuelve a esquinar los cuadros, y los 6 segundos están calculados a ojo de buen cubero.

Reacomodando un 'sprite image' simulado, con cuadritos volando.

Por supuesto que si tenemos una foto dividida en cuadritos no nos vamos a conformar con una animación tan elemental. Las posibilidades son enormes, y más si las combinamos con el diseño de la imagen.

See the Pen Rotate image 180° like a puzzle (effect "whirlpool"). by solipsistaCP (@solipsistacp) on CodePen.

Aquí tenemos un giro de cada cuadro combinado con una traslación a su punto simétrico opuesto, lo que termina en una rotación de toda la foto en 180°. La animación está demorada en cada uno para que el efecto sea más vistoso. Y tiene sentido porque la obra se puede interpretar de cualquiera de los dos lados (se nota claramente que está girada viendo la sombra del marco).

El escript es un poco menos complejo, entendiendo el anterior se pueden seguir los pasos sin problemas. Como mucho habrá que leerlo un par de veces.

onload = corta; //EJECUTA LA FUNCIÓN DE INICIO var contenedor, filas, columnas, ancho, alto; //VARIABLES GLOBALES /* DESCOMPONE Y REARMA LA IMAGEN EN DIV'S */ function corta() { /* REFIERE AL CONTENEDOR */ contenedor = document.querySelector("figure"); /* CAPTURA LA RUTA DE IMAGEN */ ruta = contenedor.querySelector("img").src; /* LEE EL TAMAÑO DEL CONTENEDOR (QUE ES IGUAL AL DE LA IMAGEN) */ ancho = contenedor.offsetWidth; alto = contenedor.offsetHeight; /* CALCULA CUÁNTOS CUADROS DE 20px CABEN EN ANCHO Y ALTO */ columnas = (ancho/20); filas = (alto/20); /* BORRA LA ETIQUETA DE IMAGEN */ contenedor.innerHTML = ""; /* ARMA LA TABLA DE DIV'S CON POSICIONES ABSOLUTAS */ for(f=0; f<filas; f++) { for(c=0; c<columnas; c++) { cuadro = document.createElement('div'); cuadro.style.left = (c*20)+"px"; cuadro.style.top = (f*20)+"px"; cuadro.style.backgroundImage = "url("+ ruta +")"; cuadro.style.backgroundSize = ancho +"px "+ alto +"px"; cuadro.style.backgroundPosition = (c*-20) +"px "+ (f*-20) +"px"; contenedor.appendChild(cuadro); } } /* ASIGNA EL EVENTO PARA LA ANIMACIÓN */ contenedor.addEventListener("click", gira); } /* ANIMACIÓN */ function gira() { /* REMUEVE EL EVENTO DEL CONTENEDOR */ contenedor.removeEventListener("click", gira); /* CAMBIA EL CURSOR */ contenedor.style.cursor = "default"; /* GUARDA EN VARIABLE LA REFERENCIA AL LIST-ÍTEM DE DIV'S */ var cuadro = document.querySelectorAll("figure div"); /* VARIABLE LOCAL PARA INCREMENTAR DENTRO DEL LOOP */ var x = 0; /* RECORRE FILAS */ for(f=0; f<filas; f++) { /* RECORRE COLUMNAS EN CADA FILA*/ for(c=0; c<columnas; c++) { /* RETRASA LA ANIMACIÓN EN CADA DIV AUMENTANDO EL DELAY POR VUELTA */ cuadro[x].style.transitionDelay = f*c*25+"ms"; /* LES CAMBIA POSICIÓN HASTA SUS COORDENADAS SIMÉTRICAS */ cuadro[x].style.left = ancho-(c*20)-20+"px"; cuadro[x].style.top = alto-(f*20)-20+"px"; /* Y LES DA UNA ROTACIÓN EN "Z" DE 180° */ cuadro[x].style.transform = "rotate(180deg)"; /* SUMA 1 A LA VARIABLE PARA LA SIGUIENTE VUELTA */ x++; } } }

Como en todos los casos presentados en el blog, el mayor trabajo lo hace el CSS, javascript solamente le pasa los valores calculados; así que mientras ven estos códigos no se olviden de mirar la hoja de estilos para ir entendiendo cómo los cambios terminan creando cada efecto.

  • Empezamos, como es costumbre, con un evento que inicia los cálculos y formatos al cargarse el documento. Seguido de las variables globales que vamos a usar después.
  • La función que tabula los div's con cada porción de imagen es practicamente igual a la anterior. La gran diferencia es que ya no tenemos elementos flotados, sino que cada cuadro tiene posición absoluta y entonces no sólo hay que ubicar con coordenadas negativas los fondos sino también con coordenadas positivas cada div. Lo que no supone ninguna complicación: el mecanismo es el mismo y empezando de 0:0 el siguiente a la derecha está a 20 pixeles y la siguiente fila a otros 20 hacia abajo (porque esta vez son de 20px de lado).
  • La función que mueve los cuadros es mucho más sencilla que la anterior. Como no se repite ni total ni parcialmente, lo primero que hace es quitar el evento para que no haya nada que clickear cuando empiece la animación; y lo segundo es cambiar el puntero para que no señale nada que se pueda picar.
  • Las variables locales sirven para referir a la colección de div's, y para crear un valor (que empieza en 'cero') y que se va incrementando en cada vuelta de los for() en '1', independientemente de las variables propias del loop, y que servirá como índice del list-ítem para apuntar a cada cuadro desde el primero hasta el último. Esto es porque los bucles siguen usando la mecánica del barrido por fila y por columna anidada. Entonces si queremos recorrerlos del primero al septingentésimo sexagésimo octavo, la combinación de coordenadas no nos sirve y nos valemos de esta variable agregada que es independiente de los bucles.
  • En la asignación de formatos el transition-delay es crítico, es lo que va a dar la vistosidad al efecto que parece descomponer la figura de a poco. Esto se logra simulando una progresión logarítmica en vez de lineal, y para eso aprovechamos justamente las variables incrementales de los bucles, que multiplicadas entre sí (y por algún valor más que aumenta apenas la demora) crean ese movimiento en remolino. [Nota: pueden probar otras combinaciones para inventar más efectos.]
  • La posición final de cada cuadro se asigna igual que al comienzo, pero con valores medidos desde el lado opuesto, así terminan con una simetría radial. Además le agregamos una rotación en "z" de 180° para que la imagen final quede realmente girada a nuestra vista. Un detalle del CSS es que el eje rotación está puesto en el centro, por lo que al girar el cuadro, su punto de inicio sigue quedando arriba a la izquierda. Ése es el motivo para restar "un ancho" a su destino en "x" y "un alto" a su destino en "y" (ambos de 20); de otra forma la esquina de origen quedaría sobre el ángulo inferior derecho del contenedor ... y su primer cuadrito oculto por estar fuera del borde. En realidad, toda la imagen estaría desplazada. Usando transform-origin:left top nos ahorramos el valor de corrección, pero el efecto se ve distinto. Es algo más para que prueben.
  • La última línea es la que incrementa la variable "x" en 1; así en la siguente vuelta (sea de la fila o columna que fuere) el formato se va a asignar al div que corresponda en el orden del DOM usando su list-item; que es sin duda el modo más práctico. [Otra nota: no es que no se pueda calcular a través de variables como "c" o "f", pero el método es más freak que ninja así que por ahora lo vamos a dejar así.]

Galería de imágenes, animación estilo "papel picado"

Les muestro un último efecto. Lo cierto es que era para el anuario 2016; pero creo que si vinieron siguiendo todos los artículos y le ponen voluntad, lo van a comprender.

Es muy enrevesado para posicionar los cuadros, hay que destripar bastante hasta entender por qué se restan o suman largos de lado por todas partes ... y es por eso que lo publico sin ninguna explicación. Da menos trabajo si experimentan con él —especialmente para mí ;-) —.

See the Pen Manual slider (images "effect confetti"). by solipsistaCP (@solipsistacp) on CodePen.

Ahora el invento ya empieza a ser más práctico. Sirve como rotador de imágenes manual y hay medidas en variables para hacerlo más configurable.

Tiene funciones reutilizables para no repetir código idéntico en cada una que arma la imagen, y alguna cosa nueva como el método push() para arrays.

Nada que no estén en condiciones de resolver, tomando su debido tiempo. Porque hay una continuación del artículo que veremos más adelante, cuando hayamos practicado un poco más estos mecanismos.

Furoya: Autor del artículo

Artículo original de Furoya.
La intención del autor con sus colaboraciones no es que los artículos sirvan para hacer un copy&paste de códigos sino que comprendas y aprendas la lógica y el cómo trabaja javaScript.
Y a partir de lo expuesto experimentes tú.
El autor del post y el editor del blog te animamos a que plantees tus dudas o reflexiones y que compartas tus realizaciones en base a lo expuesto en los comentarios. Recuerda que puedes incluir pens (ejemplos en Codepen.io) en ellos.