Throughout my first tutorial, we rebuilt a grid expertise from a pleasant web site known as Palmer, and I wrote that rebuilding present interactions from scratch is an unimaginable solution to study. It trains your eye for element, helps you grasp the underlying logic, and sharpens your artistic problem-solving.
At the moment, we’ll work on rebuilding a clean scrolling animation from the Telescope web site, initially created by Louis Paquet, Kim Levan, Adrien Vanderpotte, and Koki-Kiko. The purpose, as all the time, is to know how this sort of interplay works below the hood and to code the fundamentals from scratch.
On this tutorial, you’ll learn to simply create and animate a deconstructed picture grid and add a trailing zoom impact on a masked picture between cut up textual content that strikes aside, all primarily based on clean scrolling. We’ll be doing all this with GSAP, utilizing its ScrollSmoother and ScrollTrigger plugins, which at the moment are freely out there to everybody 🎉.
When growing interactive experiences, it helps to interrupt issues down into smaller elements. That method, each bit will be dealt with step-by-step with out feeling overwhelming.
Right here’s the construction I adopted for this impact:
- Floating picture grid
- Major visible and cut up textual content
- Layered zoom and depth impact
Let’s get began!
Floating picture grid
The Markup
Earlier than beginning the animation, we’ll start with the fundamentals. The structure may look deconstructed, but it surely wants to remain easy and predictable. For the construction itself, all we have to do is add a couple of photographs.
The Model
.section__images {
place: absolute;
prime: 0;
left: 0;
width: 100vw;
peak: 100vh;
perspective: 100vh;
img {
place: absolute;
width: 10vw;
@media (max-width: 768px) {
width: 20vw;
}
&:nth-of-type(1) {
prime: 15vw;
left: -3vw;
}
&:nth-of-type(2) {
prime: 5vw;
left: 20vw;
}
/* similar for all different photographs */
}
}
On the subject of styling, there are a couple of essential issues to notice. We arrange a full-screen part that comprises all of the floating photographs.
This part makes use of a perspective worth to allow animations alongside the Z-axis, including depth to the composition. Inside this part, every picture is positioned completely to create an natural, scattered association. By assigning their width in viewport models (vw), the photographs scale proportionally with the browser dimension, retaining the structure balanced throughout completely different display resolutions.

The Animation
First, we’ll use the ScrollSmoother plugin to introduce a refined scroll inertia, giving the scrolling expertise a smoother and extra refined really feel. We’ll additionally allow the normalizeScroll choice, since we initially bumped into some efficiency inconsistencies that affected the smoothness of the animation.
const scroller = ScrollSmoother.create({
wrapper: ".wrapper",
content material: ".content material",
clean: 1.5,
results: true,
normalizeScroll: true
})
A single GSAP timeline is all we have to deal with the whole animation. Let’s begin by setting it up with the ScrollTrigger plugin.
this.timeline = gsap.timeline({
scrollTrigger: {
set off: this.dom,
begin: "prime prime",
finish: "backside prime",
scrub: true,
pin: true
}
})
Subsequent, we’ll animate the smaller photographs by transferring them alongside the Z-axis. To make the movement really feel extra dynamic, we’ll add a stagger, introducing a small delay between every picture in order that they don’t all animate on the similar time.
this.timeline.to(this.smallImages, {
z: "100vh",
length: 1,
ease: "power1.inOut",
stagger: {
quantity: 0.2,
from: "middle"
}
})
Major visible and cut up textual content
Now that we’ve constructed the floating picture grid, it’s time to give attention to the centerpiece of the animation — the primary picture and the textual content that strikes aside to disclose it. This half will convey the composition collectively and create that clean, cinematic transition impact.
Add the big picture as a full-size cowl utilizing absolute positioning, and outline a CSS variable --progress that we’ll use later to manage the animation. This variable will make it simpler to synchronize the scaling of the picture with the movement of the textual content components.
--progress: 0;
.section__media {
place: absolute;
prime: 0;
left: 0;
width: 100%;
peak: 100%;
z-index: -1;
rework: scale(var(--progress));
img {
width: 100%;
peak: 100%;
object-fit: cowl;
}
}
For the picture animation, we’ll take a barely completely different strategy. As an alternative of animating the scale property instantly with GSAP, we’ll animate a CSS variable known as --progress all through the timeline. This methodology retains the code cleaner and permits for smoother synchronization with different visible components, reminiscent of textual content or overlay results.
onUpdate: (self) => {
const easedProgress = gsap.parseEase("power1.inOut")(self.progress)
this.dom.model.setProperty("--progress", easedProgress)
}
Animating a CSS variable like this provides you extra flexibility, for the reason that similar variable can affect a number of properties directly. It’s an ideal method for retaining advanced animations each environment friendly and simple to tweak later.
Subsequent, we’ll add our textual content aspect, which is split into two elements: one sliding to the left and the opposite transferring to the fitting.
Now we simply want to make use of the --progress variable in our CSS to animate the 2 textual content elements on both sides of the picture. Because the variable updates, each textual content components will transfer aside in sync with the picture scaling, making a clean and coordinated reveal impact.
.left {
rework: translate3d(calc(var(--progress) * (-66vw + 100%) - 0.5vw), 0, 0);
}
.proper {
rework: translate3d(calc(var(--progress) * (66vw - 100%)), 0, 0);
}
With this CSS in place, each halves of the textual content slide away from the middle because the scroll progresses, completely matching the scaling of the picture behind them. The result's a clean, synchronized movement that feels pure and balanced, reinforcing the sense of depth and focus within the composition.
Layered zoom and depth impact
This impact feels contemporary and cleverly designed, creating that good “wow” second with out being overly advanced to construct. We’ll begin by including the “entrance” photographs to our construction, that are easy duplicates of the background picture. These layers will assist us create a trailing zoom impact that provides depth and movement to the ultimate scene.
Subsequent, we’ll create and add a masks of the primary topic (on this case, a crab) to make it seem as if it’s coming out from the background. This masks will outline the seen space of every entrance picture, giving the phantasm of depth and movement because the layers scale and blur throughout the animation.

.section__media__front {
img {
mask-image: url(./masks.png);
mask-position: 50% 50%;
mask-size: cowl;
}
}
Right here we’re scaling every picture layer progressively to create a way of depth.
The primary aspect stays at its unique dimension, whereas every following layer is barely smaller to provide the impression that they’re transferring additional into the background.
.front-1 {
rework: scale(1);
}
.front-2 {
rework: scale(0.85);
}
.front-3 {
rework: scale(0.6);
}
.front-4 {
rework: scale(0.45);
}
.front-5 {
rework: scale(0.3);
}
.front-6 {
rework: scale(0.15);
}
And at last, we simply want so as to add yet another step to our timeline to convey all of the picture layers again to their unique scale (scale: 1). This ultimate movement completes the trailing impact and easily transitions the main focus towards the primary visible. The scaling animation additionally helps tie the layered depth again collectively, making the composition really feel cohesive and polished.
this.timeline.to(this.frontImages, {
scale: 1,
length: 1,
ease: "power1.inOut",
delay: .1,
}, 0.4)
To make the impact much more refined, we will add a refined blur to every layer at first after which animate it away because the timeline performs. This creates a comfortable, atmospheric look that enhances the notion of movement and depth. Because the blur fades, the scene step by step turns into sharper, drawing the viewer’s consideration towards the topic in a pure, cinematic method.
.section__media__front {
filter: blur(2px);
}
this.timeline.to(this.frontImages, {
length: 1,
filter: "blur(0px)",
ease: "power1.inOut",
delay: .4,
stagger: {
quantity: 0.2,
from: "finish"
}
}, 0.6)
With the scaling and blur animations mixed, the layered zoom impact feels wealthy and immersive. Every layer strikes in concord, giving the animation depth and fluidity whereas retaining the general expertise clean and visually balanced.
The outcome
Right here’s the ultimate end in motion. The mix of scaling, blur, and clean scrolling creates a clear, layered movement that feels each pure and visually partaking. The refined depth shift gives the look of a 3D scene coming to life as you scroll, all constructed with just some well-timed animations.
Remaining ideas
I hope you’ve discovered a couple of new issues and picked up some helpful methods whereas following this tutorial. I’m all the time amazed by how highly effective the GSAP library is and the way it permits us to create superior, polished animations with just some traces of code.
I extremely advocate testing the complete Telescope web site, which is actually a masterpiece stuffed with artistic and galvanizing results that showcase what’s doable with considerate interplay design.
Thanks for studying, and see you round 👋









