Throughout my first tutorial, we rebuilt a grid expertise from a pleasant web site referred to as Palmer, and I wrote that rebuilding current interactions from scratch is an unbelievable strategy to be taught. It trains your eye for element, helps you grasp the underlying logic, and sharpens your inventive problem-solving.
Right now, 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 objective, as all the time, is to know how this type of interplay works beneath 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 based mostly on clean scrolling. We’ll be doing all this with GSAP, utilizing its ScrollSmoother and ScrollTrigger plugins, which are actually freely obtainable to everybody 🎉.
When growing interactive experiences, it helps to interrupt issues down into smaller components. That method, every bit could be dealt with step-by-step with out feeling overwhelming.
Right here’s the construction I adopted for this impact:
- Floating picture grid
- Essential 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 format may look deconstructed, but it surely wants to remain easy and predictable. For the construction itself, all we have to do is add just a few pictures.
<div class="part">
  <div class="section__images">
    <img src="./img-1.webp" alt="Picture" />
    <img src="./img-2.webp" alt="Picture" />
    <img src="./img-3.webp" alt="Picture" />
    <img src="./img-4.webp" alt="Picture" />
    <img src="./img-9.webp" alt="Picture" />
    <img src="./img-6.webp" alt="Picture" />
    <img src="./img-7.webp" alt="Picture" />
    <img src="./img-8.webp" alt="Picture" />
    <img src="./img-9.webp" alt="Picture" />
    <img src="./img-10.webp" alt="Picture" />
  </div>
</div>The Fashion
.section__images {
  place: absolute;
  high: 0;
  left: 0;
  width: 100vw;
  peak: 100vh;
  perspective: 100vh;
  img {
    place: absolute;
    width: 10vw;
    @media (max-width: 768px) {
      width: 20vw;
    }
    &:nth-of-type(1) {
      high: 15vw;
      left: -3vw;
    }
    &:nth-of-type(2) {
      high: 5vw;
      left: 20vw;
    }
    
    /* identical for all different pictures */
  }
}With regards to styling, there are just a few essential issues to notice. We arrange a full-screen part that incorporates all of the floating pictures.
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 measurement, maintaining the format balanced throughout totally different display resolutions.

The Animation
First, we’ll use the ScrollSmoother plugin to introduce a delicate 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: "high high",
    finish: "backside high",
    scrub: true,
    pin: true
  }
})Subsequent, we’ll animate the smaller pictures by shifting 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 identical time.
this.timeline.to(this.smallImages, {
  z: "100vh",
  period: 1,
  ease: "power1.inOut",
  stagger: {
    quantity: 0.2,
    from: "middle"
  }
})Essential visible and cut up textual content
Now that we’ve constructed the floating picture grid, it’s time to deal with the centerpiece of the animation — the principle picture and the textual content that strikes aside to disclose it. This half will carry the composition collectively and create that clean, cinematic transition impact.
<div class="section__media">
  <div class="section__media__back">
    <img src="./img-big.jpg" alt="Picture" />
  </div>
</div>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;
  high: 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 totally different strategy. As an alternative of animating the scale property straight with GSAP, we’ll animate a CSS variable referred to as --progress all through the timeline. This methodology retains the code cleaner and permits for smoother synchronization with different visible components, comparable to textual content or overlay results.
onUpdate: (self) => {
  const easedProgress = gsap.parseEase("power1.inOut")(self.progress)
  this.dom.type.setProperty("--progress", easedProgress)
}Animating a CSS variable like this offers you extra flexibility, because the identical variable can affect a number of properties without delay. It’s a terrific approach for maintaining complicated animations each environment friendly and straightforward to tweak later.
Subsequent, we’ll add our textual content aspect, which is split into two components: one sliding to the left and the opposite shifting to the fitting.
<h1>
  <span class="left">for the</span>
  <span class="proper">planet</span>
</h1>Now we simply want to make use of the --progress variable in our CSS to animate the 2 textual content components 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 recent and cleverly designed, creating that good “wow” second with out being overly complicated to construct. We’ll begin by including the “entrance” pictures 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.
<div class="section__media__front front-1">
  <img src="./img-big.jpg" alt="Picture" />
</div>
<div class="section__media__front front-2">
  <img src="./img-big.jpg" alt="Picture" />
</div>
<div class="section__media__front front-3">
  <img src="./img-big.jpg" alt="Picture" />
</div>
<div class="section__media__front front-4">
  <img src="./img-big.jpg" alt="Picture" />
</div>
<div class="section__media__front front-5">
  <img src="./img-big.jpg" alt="Picture" />
</div>
<div class="section__media__front front-6">
  <img src="./img-big.jpg" alt="Picture" />
</div>Subsequent, we’ll create and add a masks of the principle 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 through 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 measurement, whereas every following layer is barely smaller to provide the impression that they’re shifting 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 eventually, we simply want so as to add yet another step to our timeline to carry all of the picture layers again to their unique scale (scale: 1). This remaining movement completes the trailing impact and easily transitions the main focus towards the principle 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,
  period: 1,
  ease: "power1.inOut",
  delay: .1,
}, 0.4)To make the impact much more refined, we will add a delicate blur to every layer initially after which animate it away because the timeline performs. This creates a delicate, atmospheric look that enhances the notion of movement and depth. Because the blur fades, the scene progressively 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, {
  period: 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 maintaining the general expertise clean and visually balanced.
The outcome
Right here’s the ultimate end in motion. The mixture of scaling, blur, and clean scrolling creates a clear, layered movement that feels each pure and visually partaking. The delicate depth shift gives the look of a 3D scene coming to life as you scroll, all constructed with just some well-timed animations.
Closing ideas
I hope you’ve realized just a few new issues and picked up some helpful tips 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 suggest trying out the complete Telescope web site, which is really a masterpiece crammed with inventive and provoking results that showcase what’s attainable with considerate interplay design.
Thanks for studying, and see you round 👋



 
                                    