★ Rated 4.9 by 200+ families
/
CQC registered
/
Carers within 48h
/
Now booking
/
★ Rated 4.9 by 200+ families
/
CQC registered
/
Carers within 48h
/
Now booking
/
★ Rated 4.9 by 200+ families
/
CQC registered
/
Carers within 48h
/
Now booking
/

GSAP instructions

Stagger card reveal

This animation reveals cards one by one using a staggered fade-up effect.

The animation targets elements with the attribute data="bottom" and separates cards into two groups:

  • Cards already visible in the viewport animate immediately on page load.
  • Remaining cards animate as they enter the viewport while scrolling.

Each card transitions from lower opacity and a downward Y offset to its original position, creating a smooth layered reveal effect.

Customization options

  • Use the attribute data="bottom" to apply the animation.
  • Adjust the stagger value to control the delay between cards.
    • Default: 0.3
    • Lower value → faster sequence
    • Higher value → more dramatic reveal
  • Customize the Y movement distance.
    • Default: 80
    • Smaller values create subtler motion.
  • Modify the animation duration.
    • Default: 0.8s
  • Change the easing style for different motion feels.
    • power2.out → smooth and modern
    • power3.out → softer finish
  • Adjust the ScrollTrigger start position.
    • Default: "top 85%"
    • Earlier trigger: "top 95%"
    • Later trigger: "top 70%"

Features

  • Smooth staggered card entrance animation
  • Instantly animates visible cards on page load
  • Reveals additional cards on scroll
  • Creates depth and visual rhythm in layouts
  • Fully customizable timing, spacing, and movement
  • Lightweight and performance-friendly for large grids
1gsap.registerPlugin(ScrollTrigger);
2 
3const cards = gsap.utils.toArray('[data="bottom"]');
4 
5// Helper: check if element is already in viewport
6function isInViewport(el) {
7  const rect = el.getBoundingClientRect();
8  return rect.top < window.innerHeight && rect.bottom > 0;
9}
10 
11// Split cards
12const visibleCards = cards.filter(isInViewport);
13const hiddenCards = cards.filter(card => !isInViewport(card));
14 
15 
16// ✅ 1. Animate visible cards on load
17gsap.from(visibleCards, {
18  opacity: 0,
19  y: 80,
20  duration: 0.8,
21  ease: "power2.out",
22  stagger: 0.3
23});
24 
25 
26// ✅ 2. Animate remaining cards on scroll
27hiddenCards.forEach((card, i) => {
28  gsap.from(card, {
29    opacity: 0,
30    y: 80,
31    duration: 0.8,
32    ease: "power2.out",
33    delay: i * 0.1,
34    scrollTrigger: {
35      trigger: card,
36      start: "top 85%",
37      toggleActions: "play none none none"
38    }
39  });
40});
41

Counter animation

This animation creates a counting effect by animating numbers from 0 to a specified target value when the element enters the viewport.

The animation targets elements with the attribute data-counter. Each element reads its target number directly from the attribute value and smoothly increments the number using GSAP.

The animation is triggered using ScrollTrigger when the element reaches 85% of the viewport.

Customization options

  • Use the attribute data-counter to define the target number.
    • Example: data-counter="150"
  • Adjust the animation duration to control counting speed.
    • Default: 2s
  • Customize the easing for different motion styles.
    • power1.out → smooth deceleration
    • power2.out → softer finish
  • Change the ScrollTrigger start point.
    • Default: "top 85%"
    • Earlier trigger: "top 95%"
    • Later trigger: "top 60%"
  • Use snap to create whole-number counting instead of decimals.
  • Add prefixes or suffixes using additional text elements.
    • Example: +150
    • Example: 150K

Features

  • Smooth animated number counting effect
  • Triggered on scroll using ScrollTrigger
  • Automatically reads values from attributes
  • Supports customizable speed and trigger position
  • Lightweight and performance-friendly
  • Perfect for stats, achievements, and metrics sections
1<script>
2  gsap.registerPlugin(ScrollTrigger);
3
4  const counters = document.querySelectorAll("[data-counter]");
5
6  counters.forEach((counter) => {
7
8    // Original value from attribute
9    const value = counter.getAttribute("data-counter");
10
11    // Extract numeric part
12    const target = parseFloat(value);
13
14    // Extract suffix (%, +, K+, etc.)
15    const suffix = value.replace(/[0-9.]/g, "");
16
17    // Detect decimal places from original value
18    const decimals = (value.split(".")[1] || "").match(/^\d+/)?.[0].length || 0;
19
20    const obj = { val: 0 };
21
22    gsap.to(obj, {
23      val: target,
24      duration: 2,
25      ease: "power1.out",
26
27      scrollTrigger: {
28        trigger: counter,
29        start: "top 85%",
30        toggleActions: "play none none none"
31      },
32
33      onUpdate: () => {
34        let displayValue;
35
36        if (decimals > 0) {
37          const factor = Math.pow(10, decimals);
38          displayValue = Math.floor(obj.val * factor) / factor;
39          displayValue = displayValue.toFixed(decimals);
40        } else {
41          displayValue = Math.floor(obj.val);
42        }
43
44        counter.innerText = displayValue + suffix;
45      },
46
47      onComplete: () => {
48        counter.innerText = value;
49      }
50    });
51
52  });
53</script>
54

Infinite loop carousel

This script powers an infinite sliding carousel using GSAP by cloning original items and seamlessly looping positions when reaching either boundary.

The animation targets elements with custom data-carousel attributes and sets up smooth interactive navigation:

  • Cloning: Duplicates card elements into the track so sliding forward or backward never exposes empty space.
  • Step Calculation: Automatically measures card width plus CSS gaps (gap or grid-column-gap) for accurate distance calculations.
  • Boundary Reset: Instantly resets position without animation (gsap.set) when reaching the end or beginning to maintain a seamless infinite loop.

Customization options

  • Target data attributes:
    • Component wrapper: data-carousel="component"
    • Sliding track: data-carousel="track"
    • Item cards: data-carousel="card"
    • Navigation buttons: data-carousel="btn-prev" and data-carousel="btn-next"
  • Modify transition speed:
    • Default: duration: 0.6
    • Lower value $\rightarrow$ faster slide
    • Higher value $\rightarrow$ slower transition
  • Change easing style:
    • power2.out $\rightarrow$ smooth, responsive feel
    • power3.out $\rightarrow$ stronger deceleration
    • expo.out $\rightarrow$ fast start with gradual glide
  • Responsiveness:
    • Automatic resize event listener recalibrates track offset instantly on window resize.

Features

  • Seamless infinite looping: Cloned set guarantees no layout gaps during transitions.
  • Dynamic Gap Detection: Automatically detects flexbox/grid layout gaps directly from CSS.
  • Animation Guard: Uses isAnimating flag to prevent rapid button-click glitcheing.
  • Resize Friendly: Recalculates card dimensions dynamically to prevent layout breaks on viewport changes.
  • Native Webflow Ready: Wrapped safely inside Webflow's JavaScript execution queue.
1<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
2
3<script>
4window.Webflow ||= [];
5window.Webflow.push(() => {
6  "use strict";
7
8  const carousels = document.querySelectorAll('[data-carousel="component"]');
9  if (!carousels.length) return;
10
11  carousels.forEach((carousel) => {
12    const track = carousel.querySelector('[data-carousel="track"]');
13    const prevBtn = carousel.querySelector('[data-carousel="btn-prev"]');
14    const nextBtn = carousel.querySelector('[data-carousel="btn-next"]');
15    let originalCards = [...carousel.querySelectorAll('[data-carousel="card"]')];
16
17    if (!track || !originalCards.length || !prevBtn || !nextBtn) return;
18
19    const totalOriginals = originalCards.length;
20
21    // Clone set twice to ensure no viewport gaps occur during infinite looping
22    originalCards.forEach((card) => {
23      const clone = card.cloneNode(true);
24      clone.setAttribute("data-carousel-clone", "true");
25      track.appendChild(clone);
26    });
27
28    const allCards = [...track.querySelectorAll('[data-carousel="card"]')];
29    let currentIndex = 0;
30    let isAnimating = false;
31
32    // Accurate calculation including card width and flex/grid track gaps
33    const getStepWidth = () => {
34      const cardWidth = originalCards[0].offsetWidth;
35      const trackStyle = window.getComputedStyle(track);
36      const gap = parseFloat(trackStyle.gap) || parseFloat(trackStyle.gridColumnGap) || 0;
37      return cardWidth + gap;
38    };
39
40    const moveSlider = (direction) => {
41      if (isAnimating) return;
42      isAnimating = true;
43
44      currentIndex += direction;
45      const stepWidth = getStepWidth();
46
47      gsap.to(track, {
48        x: -(currentIndex * stepWidth),
49        duration: 0.6,
50        ease: "power2.out",
51        onComplete: () => {
52          // Loop Forward Reset
53          if (currentIndex >= totalOriginals) {
54            currentIndex = 0;
55            gsap.set(track, { x: 0 });
56          }
57
58          // Loop Backward Reset
59          if (currentIndex < 0) {
60            currentIndex = totalOriginals - 1;
61            gsap.set(track, { x: -(currentIndex * stepWidth) });
62          }
63
64          isAnimating = false;
65        }
66      });
67    };
68
69    nextBtn.addEventListener("click", (e) => {
70      e.preventDefault();
71      moveSlider(1);
72    });
73
74    prevBtn.addEventListener("click", (e) => {
75      e.preventDefault();
76      moveSlider(-1);
77    });
78
79    window.addEventListener("resize", () => {
80      gsap.set(track, {
81        x: -(currentIndex * getStepWidth())
82      });
83    });
84  });
85});
86</script>
87

Image reveal with zoom effect

This script creates a high-end image reveal effect using GSAP and ScrollTrigger by sliding away an overlay while simultaneously zooming out the underlying image.

The animation targets elements with custom data="image" attributes and executes a synchronized timeline on scroll:

  • Pre-set position: Ensures the overlay covers the image (y: "0%") before coming into view to prevent visual glitches.
  • Overlay reveal: Slides the overlay element downward (y: "100%") to reveal the content beneath.
  • Image zoom-out: Simultaneously transitions the image from an enlarged scale (scale: 1.3) back to its natural size (scale: 1).
  • One-time trigger: Runs the reveal sequence once (once: true) as soon as the image container enters the viewport.

Customization options

  • Target data attributes:
    • Image element: data="image"
    • Overlay element: data="overlay"
  • Adjust overlay transition:
    • Default duration: 0.75s
    • Default ease: power2.inOut $\rightarrow$ smooth acceleration and deceleration
  • Customize scale effect:
    • Default start scale: 1.3 (130%)
    • Lower scale: 1.1 $\rightarrow$ subtle zoom
    • Higher scale: 1.5 $\rightarrow$ dramatic cinematic reveal
  • Modify animation duration:
    • Default zoom duration: 1.5s
    • Default ease: power1.out $\rightarrow$ smooth slowing finish
  • Adjust ScrollTrigger start position:
    • Default: "top 85%"
    • Earlier trigger: "top 95%"
    • Later trigger: "top 70%"

Features

  • Synchronized timeline: Combines overlay sliding and image scaling into a seamless simultaneous motion.
  • Performance optimized: Uses force3D: true to trigger GPU hardware acceleration for smooth rendering.
  • Tween safety: Kills active tweens via gsap.killTweensOf before animating to prevent conflicting animations.
  • Single-execution reveal: Sets once: true so the entrance effect plays cleanly a single time without re-triggering.
  • DOM Ready wrapper: Runs safely inside DOMContentLoaded to ensure elements exist before binding events.
1<script>
2document.addEventListener("DOMContentLoaded", () => {
3  gsap.registerPlugin(ScrollTrigger);
4
5  document.querySelectorAll('[data="image"]').forEach((image) => {
6
7    const wrapper = image.parentElement;
8    const overlay = wrapper.querySelector('[data="overlay"]');
9
10    // Prepare overlay before user sees it
11    gsap.set(overlay, {
12      y: "0%"
13    });
14
15    ScrollTrigger.create({
16      trigger: wrapper,
17      start: "top 85%",
18      once: true,
19
20      onEnter: () => {
21
22        gsap.killTweensOf([overlay, image]);
23
24        const tl = gsap.timeline();
25
26        // Overlay reveal
27        tl.to(overlay, {
28          y: "100%",
29          duration: 0.75,
30          ease: "power2.inOut"
31        });
32
33        // Smooth image zoom
34        tl.fromTo(image,
35          {
36            scale: 1.3
37          },
38          {
39            scale: 1,
40            duration: 1.5,
41            ease: "power1.out",
42            force3D: true
43          },
44          0
45        );
46
47      }
48    });
49
50  });
51});
52</script>

Staggered image zoom reveal

This script applies a smooth scale-down and fade-in animation to images as they enter the viewport using GSAP and ScrollTrigger.

The animation targets elements with the attribute data-anim="image" and handles the transition in two phases:

  • Initial State: Hides the images (opacity: 0) and scales them up slightly (scale: 1.2) before they are revealed.
  • Scroll Entrance: As each image scrolls into view, it smoothly fades in to full opacity (opacity: 1) while zooming out to its original size (scale: 1).
  • Staggered Delay: Adds an incremental delay based on the index (i * 0.05) to create a subtle sequential reveal when multiple images enter the viewport together.

Customization options

  • Target data attribute:
    • Image element: data-anim="image"
  • Adjust initial image state:
    • Default scale: 1.2 (120%)
    • Lower scale: 1.05 $\rightarrow$ subtle zoom effect
    • Higher scale: 1.4 $\rightarrow$ dramatic entrance
  • Modify animation duration & delay:
    • Default duration: 1s
    • Default delay stagger: i * 0.05
    • Higher stagger multiplier: i * 0.15 $\rightarrow$ more pronounced sequence
  • Change easing style:
    • power3.out $\rightarrow$ soft, smooth deceleration finish
    • power2.out $\rightarrow$ standard modern ease
    • back.out(1.7) $\rightarrow$ subtle pop-in effect
  • Adjust ScrollTrigger start position:
    • Default: "top 85%"
    • Earlier trigger: "top 95%"
    • Later trigger: "top 70%"

Features

  • Subtle staggered entrance: Sequential delay prevents all simultaneous images from triggering abruptly together.
  • Clean single play action: toggleActions: "play none none none" ensures the animation plays once cleanly without resetting on scroll up.
  • Smooth visual depth: Combining opacity fades with scale-down motion gives layout images an elevated cinematic feel.
  • Lightweight & fast: Direct GSAP tweens provide optimal performance across complex grid layouts.
1<script>
2  // =========================
3  // IMAGE ANIMATION
4  // =========================
5
6  const images = gsap.utils.toArray('[data-anim="image"]');
7
8  gsap.set(images, {
9    opacity: 0,
10    scale: 1.2
11  });
12
13  images.forEach((image, i) => {
14
15    gsap.to(image, {
16
17      opacity: 1,
18      scale: 1,
19
20      duration: 1,
21      ease: "power3.out",
22
23      delay: i * 0.05,
24
25      scrollTrigger: {
26        trigger: image,
27        start: "top 85%",
28        toggleActions: "play none none none"
29      }
30
31    });
32
33  });
34
35</script>

Split word reveal with Lenis smooth scroll

This script integrates Lenis smooth scrolling with GSAP ScrollTrigger and builds a custom split-word typography mask reveal animation.

The script targets elements with data-anim="split-words" and processes them through two core systems:

  • Lenis Smooth Scroll Integration: Connects Lenis scroll updates directly to GSAP's ScrollTrigger.update loop for frame-perfect scroll synchronizations.
  • Custom Text Splitting Engine: Parses typography node trees to wrap individual words into double-layered <span> elements:
    • Outer Wrapper: Applies overflow: hidden to mask the word while using custom padding fixes to prevent descender letters (g, y, p, q) and italic slants from getting cut off.
    • Inner Wrapper: Handles vertical transformations (yPercent) driven by GSAP.
  • Scroll-Triggered Reveal: Shifts all split words down by 110% initially, then smoothly animates them up into view (yPercent: 0) in a cascading stagger sequence when scrolled into view.

Customization options

  • Target data attribute:
    • Animated element: data-anim="split-words"
  • Modify word animation speed & stagger:
    • Default duration: 1s
    • Default stagger: 0.045s between words
    • Faster stagger: 0.02s $\rightarrow$ snappy, rapid line text reveal
    • Slower stagger: 0.08s $\rightarrow$ pronounced word-by-word sequence
  • Change easing style:
    • power4.out $\rightarrow$ strong initial burst with smooth deceleration
    • power2.out $\rightarrow$ standard subtle transition
    • back.out(1.4) $\rightarrow$ playful bounce-in effect
  • Adjust ScrollTrigger start position:
    • Default: "top 92%"
    • Earlier trigger: "top 98%"
    • Later trigger: "top 75%"

Features

  • Lenis Smooth Scroll Support: Auto-detects Lenis to bind requestAnimationFrame and keep ScrollTrigger synchronized without extra setup.
  • Native Text Splitting Fixes: Built-in DOM parser preserves formatting tags (<em>, <span>, <br>), space gaps, and prevents clipping on letter descenders.
  • Accessibility Focused: Automatically respects prefers-reduced-motion: reduce settings by bypassing animations for users sensitive to motion.
  • One-time execution: Uses once: true to fire the entrance reveal once as elements enter the viewport.
  • Safe plugin registration: Guards against missing GSAP/ScrollTrigger dependencies to prevent runtime JavaScript errors.
1<script>
2  document.addEventListener("DOMContentLoaded", function () {
3
4    if (!window.gsap) return;
5
6    gsap.registerPlugin(ScrollTrigger);
7
8    /* ==========================================================
9       LENIS SMOOTH SCROLL
10       (Optional - Only runs if Lenis is loaded)
11    ========================================================== */
12
13    if (window.Lenis) {
14
15      var lenis = new Lenis({
16        lerp: 0.1,
17        smoothWheel: true
18      });
19
20      function raf(time) {
21        lenis.raf(time);
22        requestAnimationFrame(raf);
23      }
24
25      requestAnimationFrame(raf);
26
27      lenis.on("scroll", ScrollTrigger.update);
28
29    }
30
31
32    
33
34    /* ==========================================================
35       SPLIT WORD REVEAL
36  
37       Usage:
38  
39       <h2 data-anim="split-words">
40          Your Heading
41       </h2>
42    ========================================================== */
43
44
45    window.addEventListener("DOMContentLoaded", function () {
46      // 1. Safely register ScrollTrigger plugin (Webflow Template Requirement)
47      if (typeof gsap !== "undefined" && typeof ScrollTrigger !== "undefined") {
48        gsap.registerPlugin(ScrollTrigger);
49      } else {
50        return;
51      }
52
53      // 2. Custom Split-Words Function (Inline Styling & Descender Clipping Fix)
54      function splitWords(el) {
55        if (el.__sp) return null;
56        el.__sp = true;
57
58        if (el.dataset.orig === undefined) {
59          el.dataset.orig = el.innerHTML;
60        }
61
62        var source = document.createElement("div");
63        source.innerHTML = el.dataset.orig;
64
65        var fragment = document.createDocumentFragment();
66        var words = [];
67
68        (function walk(node, italic) {
69          [].forEach.call(node.childNodes, function (child) {
70            if (child.nodeType === 3) {
71              // Text Node
72              child.textContent.split(/\s+/).forEach(function (word) {
73                if (!word) return;
74
75                // Outer wrapper for clipping/masking
76                var wrap = document.createElement("span");
77                wrap.style.display = "inline-block";
78                wrap.style.overflow = "hidden";
79                wrap.style.verticalAlign = "top";
80
81                /* Fixes clipping on descenders (g, y, p, q) & italic slants */
82                wrap.style.paddingBottom = "0.25em";
83                wrap.style.marginBottom = "-0.25em";
84                wrap.style.paddingRight = "0.05em";
85                wrap.style.paddingLeft = "0.05em";
86
87                // Inner wrapper for GSAP transforms
88                var inner = document.createElement("span");
89                inner.style.display = "inline-block";
90                inner.style.willChange = "transform";
91
92                if (italic) {
93                  inner.style.fontStyle = "italic";
94                }
95
96                inner.textContent = word;
97                wrap.appendChild(inner);
98
99                fragment.appendChild(wrap);
100                fragment.appendChild(document.createTextNode(" ")); // Preserve spaces
101
102                words.push(inner);
103              });
104            } else if (child.nodeType === 1) {
105              // Element Node
106              if (child.tagName === "BR") {
107                fragment.appendChild(document.createElement("br"));
108                return;
109              }
110
111              walk(
112                child,
113                italic ||
114                child.tagName === "EM" ||
115                child.tagName === "SPAN" ||
116                (child.classList && child.classList.contains("rt-em"))
117              );
118            }
119          });
120        })(source, false);
121
122        el.innerHTML = "";
123        el.appendChild(fragment);
124
125        return words;
126      }
127
128      // Respect Accessibility: Skip animation if user prefers reduced motion
129      if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
130        return;
131      }
132
133      // 3. Target and Animate Webflow Elements
134      var targets = gsap.utils.toArray('[data-anim="split-words"]');
135      if (!targets.length) return;
136
137      targets.forEach(function (el) {
138        var words = splitWords(el);
139        if (!words || !words.length) return;
140
141        // Set initial transform state
142        gsap.set(words, { yPercent: 110 });
143
144        ScrollTrigger.create({
145          trigger: el,
146          start: "top 92%",
147          once: true,
148          onEnter: function () {
149            gsap.to(words, {
150              yPercent: 0,
151              duration: 1,
152              stagger: 0.045,
153              ease: "power4.out"
154            });
155          }
156        });
157      });
158    });
159
160    /* ==========================================================
161       REFRESH SCROLLTRIGGER
162    ========================================================== */
163
164    ScrollTrigger.refresh();
165
166
167    // ================================================
168
169  });
170</script>

FAQ accordion animation

This interaction creates a smooth accordion animation for FAQ sections, allowing users to expand and collapse answers with a single click.

The animation targets FAQ elements using custom attributes and synchronizes the answer reveal with a rotating icon for a polished user experience.

  • The FAQ wrapper uses the attribute faq="item".
  • The clickable icon uses the attribute faq="icon".
  • The answer container uses the attribute faq="answer".
  • Clicking an FAQ item expands its answer while rotating the icon from to 90°.
  • Clicking the item again collapses the answer and returns the icon to its original position.

 

Customization options

  • Use the attribute faq="item" to define each FAQ accordion item.
  • Use the attribute faq="icon" for the rotating indicator.
    • Default rotation: 0° → 90°
    • You can change the rotation angle to match your design.
  • Use the attribute faq="answer" for the collapsible content.
    • Default animation: height: 0 → auto
    • You can also animate opacity for a softer reveal.
  • Adjust the animation duration.
    • Default: 0.5s
    • Lower value → faster interaction
    • Higher value → smoother transition
  • Modify the easing style.
    • power2.out → smooth and responsive
    • power3.out → softer finish
    • none → linear animation

 

Features

  • Smooth accordion open and close animation
  • Animated icon rotation from to 90°
  • Expands answer height from 0 to auto
  • Supports multiple FAQ items using reusable attributes
  • Clean and lightweight GSAP implementation
  • Easy to customize timing, easing, and rotation angle
  • Improves readability and user experience for FAQ sections

Marquee

This animation creates a continuous horizontal scrolling effect by moving elements from their original position to the left across the screen.

The animation targets elements with the attribute  visiond= "marquee" and transitions the X position from 0% to -100%, creating a seamless marquee movement commonly used for logos, announcements, or looping text sections.

Customization options

  • Use the attribute data= "marquee" to apply the animation.
  • Adjust the animation speed by changing the duration.
    • Lower duration → faster movement
    • Higher duration → slower movement
  • Modify the movement direction by changing the X values.
    • 0% → -100% → left scroll
    • 0% → 100% → right scroll
  • Enable infinite looping for continuous movement.
  • Add linear easing for a consistent scrolling speed.
    • Recommended: ease: "none"
  • Combine with duplicated content for a seamless infinite marquee effect.

Features

  • Smooth infinite horizontal scrolling animation
  • Ideal for logo strips, text banners, and announcements
  • Fully customizable speed and direction
  • Lightweight and performance-friendly
  • Creates dynamic movement and visual engagement