Skip to Content
PluginsPinned Image Masks

Pinned Image Masks

The pinned-image-masks plugin creates a compelling, pinned scrolling section where a stack of images sequentially reveals the image underneath (by masking/reducing height), while corresponding text content fades in and out in sync with the images. It uses GSAP  and ScrollTrigger  to pin the parent element and scrub the animations based on the scroll position.

In development, it is implemented in assets/js/modules/pinned-image-masks.js and initialized automatically by assets/js/main.js.

Note on Screen Sizes: The pinned image masks animation is specifically designed for larger screens. It utilizes gsap.matchMedia() to only initialize and play the animation on viewports matching the lg breakpoint (typically desktop and larger screens). On smaller screens, the elements will display according to their default CSS without the scroll-driven pinning effect.

Markup Requirements

To use the plugin, wrap your content in a main container and apply the appropriate data attributes to the required elements:

  • data-pinned-image-masks: The root container that acts as the pinning wrapper and scroll trigger.
  • data-pinned-image-masks-text-content: The text elements that will fade in and out. You should have one of these for each image content block.
  • data-pinned-image-masks-image-content: The image elements (or blocks containing images) that will be animated (masked by reducing height to reveal the next image below).
  • data-pinned-image-masks-scroll-trigger: An element used to determine the height/duration of each step during the scroll sequence.

Optional Progress Tracking

If you have multiple images, you can also add a progress tracker using the following optional attributes:

  • data-pinned-image-masks-progress: The container for the progress elements. It will be made visible (display: flex) if there is more than one image.
  • data-pinned-image-masks-progress-bar-indicator: The element whose height or width will be updated from 0% to 100% based on scroll progress.
  • data-pinned-image-masks-progress-current: The text element displaying the current active index.
  • data-pinned-image-masks-progress-total: The text element displaying the total number of images.

Markup Example

Below is an example of how to structure the HTML for the pinned image masks effect. Notice how it provides a standard stacked layout for mobile (lg:hidden) and a specific structure for the scroll-driven animation on desktop (hidden lg:flex):

<section data-pinned-image-masks class="overflow-hidden bg-black text-white py-20"> <div class="container mx-auto px-4"> <!-- Start: Mobile View (Stacked layout, visible only on small screens) --> <div class="grid grid-cols-1 gap-10 lg:hidden"> <!-- Item 1 --> <div class="grid grid-cols-1 gap-8"> <div> <h3 class="mb-6 text-2xl font-medium">First Item Title</h3> <p class="text-lg text-muted-foreground">Description for the first item.</p> </div> <figure class="relative aspect-16/10 w-full"> <img src="image1.jpg" alt="First" class="absolute inset-0 h-full w-full rounded-2xl object-cover" /> </figure> </div> <!-- Item 2 --> <div class="grid grid-cols-1 gap-8"> <div> <h3 class="mb-6 text-2xl font-medium">Second Item Title</h3> <p class="text-lg text-muted-foreground">Description for the second item.</p> </div> <figure class="relative aspect-16/10 w-full"> <img src="image2.jpg" alt="Second" class="absolute inset-0 h-full w-full rounded-2xl object-cover" /> </figure> </div> </div> <!-- End: Mobile View --> <!-- Start: Desktop View (Pinned and animated layout, visible only on lg screens) --> <div class="hidden gap-5 lg:flex" data-pinned-image-masks-scroll-trigger> <div class="grid grow grid-cols-2 items-center gap-10"> <!-- Text Area --> <div class="relative h-full w-full"> <!-- Text Item 1 --> <div data-pinned-image-masks-text-content class="absolute inset-0 h-full translate-y-full opacity-0 flex flex-col justify-center"> <h3 class="mb-6 text-4xl font-medium">First Item Title</h3> <p class="text-lg text-muted-foreground">Description for the first item.</p> </div> <!-- Text Item 2 --> <div data-pinned-image-masks-text-content class="absolute inset-0 h-full translate-y-full opacity-0 flex flex-col justify-center"> <h3 class="mb-6 text-4xl font-medium">Second Item Title</h3> <p class="text-lg text-muted-foreground">Description for the second item.</p> </div> </div> <!-- Image Area --> <div class="relative aspect-16/10 w-full"> <div class="absolute inset-0 overflow-hidden rounded-2xl"> <!-- Image Item 1 --> <img src="image1.jpg" alt="First" class="absolute inset-0 h-full w-full object-cover" data-pinned-image-masks-image-content /> <!-- Image Item 2 --> <img src="image2.jpg" alt="Second" class="absolute inset-0 h-full w-full object-cover" data-pinned-image-masks-image-content /> </div> </div> </div> <!-- Start: Progress Indicator --> <div data-pinned-image-masks-progress class="hidden shrink-0 flex-col items-center justify-between gap-2 text-2xl font-medium"> <div data-pinned-image-masks-progress-current>01</div> <div data-pinned-image-masks-progress-bar class="w-[4px] grow overflow-hidden rounded-full bg-white/20"> <div data-pinned-image-masks-progress-bar-indicator class="w-full bg-white"></div> </div> <div data-pinned-image-masks-progress-total>02</div> </div> <!-- End: Progress Indicator --> </div> <!-- End: Desktop View --> </div> </section>

Demo Example

View the complete code sample on this page.

StatixFlow Pinned Image Masks ExampleHomepage
Last updated on