( COMPONENTES )
Revelado de imagen pixelado
Documentación
Carga primero las librerías externas. Después copia la estructura y añade los bloques de código indicados a continuación.
Preparación: scripts externos
Pega este script en Before </body>, antes del JavaScript del componente. Si tu página ya carga GSAP, conserva una sola carga.
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>1. Copia la estructura a Webflow
Selecciona el contenedor de destino en el Designer y pega con Ctrl+V o Cmd+V. Las capas, las clases y los estilos base se entregan en el formato nativo de Webflow.
2. Añade el CSS custom
Pégalo en Page settings → Custom code → Inside <head>. Este bloque conserva los estados, selectores especiales y reglas responsive; las animaciones también se incluyen aquí cuando existen.
<style>
.cloneable { --container-padding: 2em; --color-neutral-800: #131313; --color-primary: #ff4c24;
}
.pixelated-image-card { overflow: hidden;
}
.pixelated-image-card__img { object-fit: cover; }
.pixelated-image-card__pixels { pointer-events: none; }
@media (max-width: 1024px) {
.pixelated-image-card { width: 100%; max-width: 30em; }
}
</style>3. Añade el JavaScript
Pégalo en Page settings → Custom code → Before </body>. Se inicializa al cargar la página. Mantén las clases y los atributos originales; puedes duplicar el componente sin duplicar el script.
<script>
function initPixelatedImageReveal() {
const animationStepDuration = 0.3;
const gridSize = 7;
const pixelSize = 100 / gridSize;
const cards = document.querySelectorAll('[data-pixelated-image-reveal]');
const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)');
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
cards.forEach((card) => {
const pixelGrid = card.querySelector('[data-pixelated-image-reveal-grid]');
const activeCard = card.querySelector('[data-pixelated-image-reveal-active]');
if (!pixelGrid || !activeCard || card.dataset.pixelatedReady) return;
card.dataset.pixelatedReady = 'true';
pixelGrid.querySelectorAll('.pixelated-image-card__pixel').forEach(pixel => pixel.remove());
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const pixel = document.createElement('div');
pixel.classList.add('pixelated-image-card__pixel');
pixel.style.width = `${pixelSize}%`;
pixel.style.height = `${pixelSize}%`;
pixel.style.left = `${col * pixelSize}%`;
pixel.style.top = `${row * pixelSize}%`;
pixelGrid.appendChild(pixel);
}
}
const pixels = pixelGrid.querySelectorAll('.pixelated-image-card__pixel');
const staggerDuration = animationStepDuration / pixels.length;
let isActive = false;
let delayedCall;
const controller = new AbortController();
const options = { signal: controller.signal };
const animatePixels = (activate) => {
isActive = activate;
gsap.killTweensOf(pixels);
if (delayedCall) delayedCall.kill();
gsap.set(pixels, { display: 'none' });
if (reducedMotion.matches) {
activeCard.style.display = activate ? 'block' : 'none';
return;
}
gsap.to(pixels, {
display: 'block', duration: 0,
stagger: { each: staggerDuration, from: 'random' }
});
delayedCall = gsap.delayedCall(animationStepDuration, () => {
activeCard.style.display = activate ? 'block' : 'none';
activeCard.style.pointerEvents = activate ? 'none' : '';
});
gsap.to(pixels, {
display: 'none', duration: 0, delay: animationStepDuration,
stagger: { each: staggerDuration, from: 'random' }
});
};
card.addEventListener('click', () => {
if (!finePointer.matches) animatePixels(!isActive);
}, options);
card.addEventListener('mouseenter', () => {
if (finePointer.matches && !isActive) animatePixels(true);
}, options);
card.addEventListener('mouseleave', () => {
if (finePointer.matches && isActive) animatePixels(false);
}, options);
reducedMotion.addEventListener('change', () => animatePixels(isActive), options);
window.addEventListener('pagehide', () => {
controller.abort();
gsap.killTweensOf(pixels);
if (delayedCall) delayedCall.kill();
delete card.dataset.pixelatedReady;
}, { once: true });
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPixelatedImageReveal, { once: true });
} else initPixelatedImageReveal();
</script>4. Personaliza y comprueba
Edita los textos, colores y espaciados base desde el Designer. Revisa el comportamiento en la vista previa con custom code habilitado o en el sitio publicado. Prueba también el teclado y la vista móvil.
Para integrarlo en una web con código, utiliza los bloques por separado y conserva las clases y los atributos de la estructura.
Preparación: scripts externos
Carga GSAP antes del JavaScript del componente. Incluye la librería una sola vez en tu página.
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>Añade la estructura en tu página.
<section class="cloneable">
<div data-hover="" data-pixelated-image-reveal="" class="pixelated-image-card">
<div class="before__100"></div>
<div class="pixelated-image-card__default">
<img src="https://cdn.prod.website-files.com/6712ad33825977f9d2f1ba2c/6714d43a777a77da89a9b5ec_osmo-pixelated-image-1.jpg" width="400" alt="Retrato antes del revelado" class="pixelated-image-card__img">
</div>
<div data-pixelated-image-reveal-active="" class="pixelated-image-card__active">
<img src="https://cdn.prod.website-files.com/6712ad33825977f9d2f1ba2c/6714d43a4d1abab1b3c81caf_osmo-pixelated-image-2.jpg" width="400" alt="Retrato después del revelado" class="pixelated-image-card__img">
</div>
<div data-pixelated-image-reveal-grid="" class="pixelated-image-card__pixels" aria-hidden="true">
<div class="pixelated-image-card__pixel"></div>
</div>
</div>
</section>Añade estos estilos a tu hoja de estilos.
.cloneable {
--container-padding: 2em;
--color-neutral-800: #131313;
--color-primary: #ff4c24;
padding: var(--container-padding);
justify-content: center;
align-items: center;
min-height: 100svh;
display: flex;
position: relative;
}
.pixelated-image-card {
background-color: var(--color-neutral-800);
color: var(--color-primary);
border-radius: .5em;
width: 30vw;
max-width: 100%;
position: relative;
overflow: hidden;
}
.before__100 { padding-top: 100%; }
.pixelated-image-card__default,
.pixelated-image-card__img,
.pixelated-image-card__active,
.pixelated-image-card__pixels {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.pixelated-image-card__img { object-fit: cover; }
.pixelated-image-card__active { display: none; }
.pixelated-image-card__pixels { pointer-events: none; }
.pixelated-image-card__pixel {
background-color: currentColor;
width: 100%;
height: 100%;
display: none;
position: absolute;
}
@media (max-width: 1024px) {
.pixelated-image-card { width: 100%; max-width: 30em; }
}Añade el script después de la estructura HTML.
function initPixelatedImageReveal() {
const animationStepDuration = 0.3;
const gridSize = 7;
const pixelSize = 100 / gridSize;
const cards = document.querySelectorAll('[data-pixelated-image-reveal]');
const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)');
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
cards.forEach((card) => {
const pixelGrid = card.querySelector('[data-pixelated-image-reveal-grid]');
const activeCard = card.querySelector('[data-pixelated-image-reveal-active]');
if (!pixelGrid || !activeCard || card.dataset.pixelatedReady) return;
card.dataset.pixelatedReady = 'true';
pixelGrid.querySelectorAll('.pixelated-image-card__pixel').forEach(pixel => pixel.remove());
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const pixel = document.createElement('div');
pixel.classList.add('pixelated-image-card__pixel');
pixel.style.width = `${pixelSize}%`;
pixel.style.height = `${pixelSize}%`;
pixel.style.left = `${col * pixelSize}%`;
pixel.style.top = `${row * pixelSize}%`;
pixelGrid.appendChild(pixel);
}
}
const pixels = pixelGrid.querySelectorAll('.pixelated-image-card__pixel');
const staggerDuration = animationStepDuration / pixels.length;
let isActive = false;
let delayedCall;
const controller = new AbortController();
const options = { signal: controller.signal };
const animatePixels = (activate) => {
isActive = activate;
gsap.killTweensOf(pixels);
if (delayedCall) delayedCall.kill();
gsap.set(pixels, { display: 'none' });
if (reducedMotion.matches) {
activeCard.style.display = activate ? 'block' : 'none';
return;
}
gsap.to(pixels, {
display: 'block', duration: 0,
stagger: { each: staggerDuration, from: 'random' }
});
delayedCall = gsap.delayedCall(animationStepDuration, () => {
activeCard.style.display = activate ? 'block' : 'none';
activeCard.style.pointerEvents = activate ? 'none' : '';
});
gsap.to(pixels, {
display: 'none', duration: 0, delay: animationStepDuration,
stagger: { each: staggerDuration, from: 'random' }
});
};
card.addEventListener('click', () => {
if (!finePointer.matches) animatePixels(!isActive);
}, options);
card.addEventListener('mouseenter', () => {
if (finePointer.matches && !isActive) animatePixels(true);
}, options);
card.addEventListener('mouseleave', () => {
if (finePointer.matches && isActive) animatePixels(false);
}, options);
reducedMotion.addEventListener('change', () => animatePixels(isActive), options);
window.addEventListener('pagehide', () => {
controller.abort();
gsap.killTweensOf(pixels);
if (delayedCall) delayedCall.kill();
delete card.dataset.pixelatedReady;
}, { once: true });
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPixelatedImageReveal, { once: true });
} else initPixelatedImageReveal();Cómo funciona
Una cuadrícula de píxeles cubre la imagen y revela el segundo retrato. Al retirar el cursor vuelve a la imagen inicial; en pantallas táctiles, cada toque alterna los retratos.
Personalización
Ajusta animationStepDuration = 0.3 para acelerar o ralentizar cada fase del revelado. Cambia gridSize = 7 para modificar la cuadrícula: un valor mayor genera más píxeles, de menor tamaño. Sustituye las dos imágenes y ajusta los colores y el tamaño de la tarjeta.
Requisitos
Carga GSAP 3.15 antes del JavaScript del componente. En Webflow, copia la estructura nativa y añade los bloques de CSS y JavaScript indicados en Custom code. Conserva las clases y los atributos data-pixelated-image-reveal; comprueba el efecto en la vista previa con custom code habilitado o en el sitio publicado.


