Hi there everybody, I’m Iqbal Muthahhary, a contract artistic net developer based mostly in Indonesia. It’s a pleasure to have the chance to encourage, educate, and study in collaboration with Codrops.
On this tutorial, we’ll discover tips on how to construct interactive grid format transitions triggered by buttons utilizing the GSAP Flip plugin, together with vanilla JavaScript and primary CSS Grid ideas. The interplay itself will stay easy: switching grid configurations and updating lively button states, whereas GSAP handles the visible continuity.
The goal is to point out how we will easily change the dimensions of grid objects and reorder them. This can be a sample that may be very fascinating, particularly for portfolio layouts and interactive galleries.
HTML Construction
This HTML is cut up into two sections, every with a particular function in controlling and displaying the grid gallery.
The primary part acts as a configuration panel. It features a set of buttons that allow the consumer select completely different grid format scales. Every button has a data-size attribute that shops the goal grid measurement worth (comparable to 50%, 75%, 100%, and so on.). The at present chosen button receives the lively class, which visually signifies the present selection and determines which worth is utilized to the gallery beneath.
The second part is the grid gallery itself. It makes use of a customized attribute, data-size-grid, to retailer the at present lively grid measurement worth. When the consumer clicks a button within the configuration panel, this attribute is up to date accordingly. Altering data-size-grid updates the CSS grid format, permitting the gallery to easily adapt to the chosen configuration.
Contained in the grid gallery, every merchandise is represented by a .grid_gallery_item. Each merchandise comprises a picture block (utilizing a background-image) and a <p> aspect that shows the merchandise’s index within the gallery. Every grid merchandise additionally defines a customized CSS variable, --aspect-ratio, which controls the picture’s facet ratio. This makes it potential to maintain proportions constant throughout layouts whereas nonetheless permitting every merchandise to have its personal distinctive form.
<nav class="options_grid_container">
<div class="configuration_grid_size">
<button data-size="50%">50%</button>
<button class="lively" data-size="75%">75%</button>
<button data-size="100%">100%</button>
<button data-size="125%">125%</button>
<button data-size="150%">150%</button>
</div>
</nav>
<part class="grid_gallery_container" id="grid-gallery" data-size-grid="75%">
<div class="grid_gallery_item" fashion="--aspect-ratio: 1 / 1">
<div class="picture" fashion="background-image: url(public/1.webp)"></div>
<p>01</p>
</div>
<div class="grid_gallery_item" fashion="--aspect-ratio: 4 / 5">
<div class="picture" fashion="background-image: url(public/2.webp)"></div>
<p>02</p>
</div>
<div class="grid_gallery_item" fashion="--aspect-ratio: 16 / 9">
<div class="picture" fashion="background-image: url(public/3.webp)"></div>
<p>03</p>
</div>
<!-- Extra objects -->
</part>
Grid Model
The gallery makes use of CSS Grid. The principle container defines the grid format, spacing, padding, and total width, whereas the data-size-grid attribute controls how compact or spacious the grid feels.
Every data-size-grid worth adjusts the variety of columns. Smaller values match extra objects on display screen, whereas bigger values give every merchandise extra room. This makes it straightforward to see how the format responds to the chosen choice.
Picture proportions are dealt with with the aspect-ratio property, utilizing a CSS variable outlined inline within the HTML. This fashion, every picture can maintain its personal form whereas the grid stays clear and constant.
.grid_gallery_container {
show: grid;
hole: 1.5rem;
padding: 2rem 0;
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
.grid_gallery_container[data-size-grid='50%'] {
grid-template-columns: repeat(16, 1fr);
}
.grid_gallery_container[data-size-grid='75%'] {
grid-template-columns: repeat(10, 1fr);
}
.grid_gallery_container[data-size-grid='100%'] {
grid-template-columns: repeat(8, 1fr);
}
.grid_gallery_container[data-size-grid='125%'] {
grid-template-columns: repeat(6, 1fr);
}
.grid_gallery_container[data-size-grid='150%'] {
grid-template-columns: repeat(4, 1fr);
}
.grid_gallery_item {
will-change: auto;
show: flex;
flex-direction: column;
}
.grid_gallery_item p {
font-size: 0.6745rem;
text-align: left;
}
.picture {
width: 100%;
aspect-ratio: var(--aspect-ratio, 1 / 1);
background-size: cowl;
background-position: heart;
filter: brightness(0.8);
transition: filter 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.picture:hover {
filter: brightness(1);
}
@media display screen and (max-width: 768px) {
.grid_gallery_container[data-size-grid='50%'],
.grid_gallery_container[data-size-grid='75%'],
.grid_gallery_container[data-size-grid='100%'],
.grid_gallery_container[data-size-grid='125%'],
.grid_gallery_container[data-size-grid='150%'] {
grid-template-columns: repeat(3, 1fr);
}
}
@media display screen and (min-width: 768px) and (max-width: 1024px) {
.grid_gallery_container[data-size-grid='50%'] {
grid-template-columns: repeat(10, 1fr);
}
.grid_gallery_container[data-size-grid='75%'] {
grid-template-columns: repeat(8, 1fr);
}
.grid_gallery_container[data-size-grid='100%'] {
grid-template-columns: repeat(6, 1fr);
}
.grid_gallery_container[data-size-grid='125%'] {
grid-template-columns: repeat(5, 1fr);
}
.grid_gallery_container[data-size-grid='150%'] {
grid-template-columns: repeat(3, 1fr);
}
}
Alright, let’s make it interactive.
JavaScript
Create a easy grid format transition utilizing the GSAP Flip plugin
Every button controls how dense the grid format feels. When a button is clicked, the code first checks whether or not an animation is already working. Whether it is, the clicking is ignored to maintain every part steady. Subsequent, it reads the goal grid measurement from the button’s data-size attribute. If that measurement is already lively, the clicking can also be ignored since there’s nothing to replace.
When an actual change is required, interplay is briefly locked and the present format of all grid objects is captured. The grid measurement is then up to date by altering the data-size-grid attribute, which triggers a brand new CSS grid format.
The lively button state is up to date to mirror the brand new choice, and GSAP Flip animates the grid easily from the earlier format to the brand new one. As soon as the animation finishes, interplay is unlocked and the grid is prepared for the following enter.
// Flag to forestall a number of animations on the similar time
// currentGridSize shops the lively grid measurement
let animated = false,
currentGridSize = gridGallery.dataset.sizeGrid || "75%";
// Loop by every configuration button
triggerButtons.forEach((btn) => {
// Add click on occasion listener to every button
btn.addEventListener("click on", () => {
// Forestall interplay if an animation is already working
if (animated) return;
// Get the goal grid measurement from data-size attribute
const targetSize = btn.dataset.measurement;
// If the clicked measurement is already lively, do nothing
if (targetSize === currentGridSize) return;
// Lock animation state
animated = true;
// Seize the present place and measurement of all grid objects
const state = Flip.getState(allGridItem);
// Replace grid measurement utilizing knowledge attribute (utilized by CSS)
gridGallery.dataset.sizeGrid = targetSize;
// Replace present grid measurement state
currentGridSize = targetSize;
// Take away "lively" class from all buttons
triggerButtons.forEach((btn) => {
btn.classList.take away("lively");
});
// Add "lively" class to the clicked button
btn.classList.add("lively");
// Animate components from the earlier state to the brand new format
Flip.from(state, {
length: 0.8, // Animation length in seconds
ease: "expo.inOut", // Easy easing for pure movement
onComplete: () => {
// Unlock animation after completion
animated = false;
},
});
});
});
Model 2
In comparison with the less complicated model, the core logic stays precisely the identical: button clicks, state checks, format updates, and the Flip animation all work in the identical means. The principle distinction right here is how the transition feels visually.
This model provides a visible impact to the grid container itself. Whereas the objects are rearranging, all the grid briefly blurs and turns into brighter, then easily returns to its regular state. This impact runs in parallel with the Flip animation and helps soften the second when the format adjustments, making the transition really feel extra polished and intentional.
This model additionally makes use of a stagger with a random order, so objects don’t all transfer on the similar time. Due to the staggered movement, the entire transition time is calculated by combining the Flip length and the stagger quantity. The interplay lock (animated) continues to be launched solely after the primary Flip animation finishes, guaranteeing every part stays steady earlier than the following interplay.
Briefly, this model doesn’t change how the grid works, however it enhances the way it feels by including depth, rhythm, and delicate visible suggestions through the transition.
// Flag to forestall a number of animations on the similar time
// currentGridSize shops the lively grid measurement
let animated = false,
currentGridSize = gridGallery.dataset.sizeGrid || "75%";
// Loop by every configuration button
triggerButtons.forEach((btn) => {
// Add click on occasion listener to every button
btn.addEventListener("click on", () => {
// Forestall interplay if an animation is already working
if (animated) return;
// Get the goal grid measurement from data-size attribute
const targetSize = btn.dataset.measurement;
// If the clicked measurement is already lively, do nothing
if (targetSize === currentGridSize) return;
// Lock animation state
animated = true;
// Seize the present place and measurement of all grid objects
const state = Flip.getState(allGridItem);
// Replace grid measurement utilizing knowledge attribute (utilized by CSS)
gridGallery.dataset.sizeGrid = targetSize;
// Replace present grid measurement state
currentGridSize = targetSize;
// Take away "lively" class from all buttons
triggerButtons.forEach((btn) => {
btn.classList.take away("lively");
});
// Add "lively" class to the clicked button
btn.classList.add("lively");
const flipDuration = 1;
const staggerAmount = 0.3;
const totalFlipDuration = flipDuration + staggerAmount;
// Animate components from the earlier state to the brand new format
Flip.from(state, {
absolute: true,
length: flipDuration, // Animation length in seconds
ease: "expo.inOut", // Easy easing for pure movement
onComplete: () => {
// Unlock animation after completion
animated = false;
},
stagger: {
quantity: staggerAmount,
from: "random",
},
}).fromTo(
gridGallery,
{
filter: "blur(0px) brightness(100%)",
willChange: "filter",
},
{
length: totalFlipDuration,
keyframes: [
{
filter: "blur(10px) brightness(200%)",
duration: totalFlipDuration * 0.5,
ease: "power2.in",
},
{
filter: "blur(0px) brightness(100%)",
duration: totalFlipDuration * 0.5,
ease: "power2",
delay: 0.5,
},
],
},
0
);
});
});
Conclusion
This instance highlights simply one among many potential instructions. By preserving the core logic the identical and solely adjusting animation particulars, the grid transition can really feel delicate, dynamic, or extra expressive. Small tweaks to timing, stagger, or visible results can already open up a variety of variations.
This flexibility makes the setup straightforward to adapt and lengthen, encouraging experimentation with out including pointless complexity.
And that’s it! We’ve constructed an interactive grid format transition utilizing the GSAP Flip plugin. I hope this tutorial was helpful. Thanks for studying 😃


