This project presents a dynamic way to transition between images. When you hover over an image, it transforms into another through a pixelated effect.
The transition breaks the image into a grid of small squares that randomly appear and disappear. This creates a digital dissolve effect between two images.
See It In Action:
How to use it:
1. Add the following HTML structure for each image card you want to include. The structure contains:
- A container with data attributes for targeting
- A default image that appears initially
- An active image that appears after the transition
- A grid container that will hold the pixels for the transition effect
<div data-hover="" data-pixelated-image-reveal="" class="pixelated-image-card">
<div class="before__100"></div>
<div class="pixelated-image-card__default">
<img src="1.jpg" width="400" alt="" class="pixelated-image-card__img">
</div>
<div data-pixelated-image-reveal-active="" class="pixelated-image-card__active">
<img src="2.jpg" width="400" alt="" class="pixelated-image-card__img">
</div>
<div data-pixelated-image-reveal-grid="" class="pixelated-image-card__pixels">
<div class="pixelated-image-card__pixel"></div>
</div>
</div>2. Add the following CSS snippets to the page. These styles create the container structure and set up the positioning for the transition components.
.pixelated-image-card {
background-color: #131313;
color: #ff4c24;
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__active {
display: none;
}
.pixelated-image-card__pixel {
background-color: currentColor;
width: 100%;
height: 100%;
display: none;
position: absolute;
}3. Add the GSAP animation library to your HTML document:
<script src="/dist/gsap.min.js"></script>
4. Add this JavaScript code to enable the pixelated transition effect:
document.addEventListener('DOMContentLoaded', function () {
const animationStepDuration = 0.3; // Adjust this value to control the timing
const gridSize = 7; // Number of pixels per row and column (adjustable)
// Calculate pixel size dynamically
const pixelSize = 100 / gridSize; // Calculate the size of each pixel as a percentage
// Select all cards
const cards = document.querySelectorAll('[data-pixelated-image-reveal]');
// Detect if device is touch device
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.matchMedia("(pointer: coarse)").matches;
// Loop through each card
cards.forEach((card) => {
const pixelGrid = card.querySelector('[data-pixelated-image-reveal-grid]');
const activeCard = card.querySelector('[data-pixelated-image-reveal-active]');
// Remove any existing pixels with the class 'pixelated-image-card__pixel'
const existingPixels = pixelGrid.querySelectorAll('.pixelated-image-card__pixel');
existingPixels.forEach(pixel => pixel.remove());
// Create a grid of pixels dynamically based on the gridSize
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}%`; // Set the pixel width dynamically
pixel.style.height = `${pixelSize}%`; // Set the pixel height dynamically
pixel.style.left = `${col * pixelSize}%`; // Set the pixel's horizontal position
pixel.style.top = `${row * pixelSize}%`; // Set the pixel's vertical position
pixelGrid.appendChild(pixel);
}
}
const pixels = pixelGrid.querySelectorAll('.pixelated-image-card__pixel');
const totalPixels = pixels.length;
const staggerDuration = animationStepDuration / totalPixels; // Calculate stagger duration dynamically
let isActive = false; // Variable to track if the card is active
let delayedCall;
const animatePixels = (activate) => {
isActive = activate;
gsap.killTweensOf(pixels); // Reset any ongoing animations
if (delayedCall) {
delayedCall.kill();
}
gsap.set(pixels, { display: 'none' }); // Make all pixels invisible instantly
// Show pixels randomly
gsap.to(pixels, {
display: 'block',
duration: 0,
stagger: {
each: staggerDuration,
from: 'random'
}
});
// After animationStepDuration, show or hide the activeCard
delayedCall = gsap.delayedCall(animationStepDuration, () => {
if (activate) {
activeCard.style.display = 'block';
// **Set pointer-events to none so clicks pass through activeCard**
activeCard.style.pointerEvents = 'none';
} else {
activeCard.style.display = 'none';
}
});
// Hide pixels randomly
gsap.to(pixels, {
display: 'none',
duration: 0,
delay: animationStepDuration,
stagger: {
each: staggerDuration,
from: 'random'
}
});
};
if (isTouchDevice) {
// For touch devices, use click event
card.addEventListener('click', () => {
animatePixels(!isActive);
});
} else {
// For non-touch devices, use mouseenter and mouseleave
card.addEventListener('mouseenter', () => {
if (!isActive) {
animatePixels(true);
}
});
card.addEventListener('mouseleave', () => {
if (isActive) {
animatePixels(false);
}
});
}
});
});How It Works:
The JavaScript code first sets up a grid of div elements. These act as the “pixels.” Each pixel is sized and positioned using percentages. This approach makes the grid responsive.
The code then uses GSAP to animate these pixels. When you hover (or tap on touch devices), the pixels are displayed one by one, but with a random start point. It creates the visual transition, and the activeCard is set to visiable. After the animation time which defined in animationStepDuration, these pixels become invisible, also with random start point.
The isTouchDevice check optimizes the interaction. On touch devices, a click triggers the effect. On non-touch devices, the effect is triggered by hovering.







