Instructions

Find easy to follow instructions

GSAP Guide

All GSAP animations used in this template are collected here. On this page, you’ll find guidance on how to locate and edit them. Each code block comes with extra notes to make it easier to understand.

You can find the code in the Embed Code inside this template.

Step 1 - Smooth scrolling

Before that, prepare the library package from GSAP for some animations. This animation makes the scrolling experience smoother across all devices.

<!-- --------- Libraries --------- -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/lenis@1.3.1/dist/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/SplitText.min.js"></script>
<script>
window.Webflow ||= [];
window.Webflow.push(() => {
  gsap.registerPlugin(ScrollTrigger, SplitText);

  /* =====================================================
     SMOOTH SCROLLING (Desktop & Tablet Only)
  ===================================================== */
if (window.matchMedia("(min-width: 768px)").matches) {

  const lenis = new Lenis({ 
    duration: 1.2,
    easing: (t) => 1 - Math.pow(1 - t, 3),
    smoothWheel: true,
    smoothTouch: false 
  });

  function raf(time) {
    lenis.raf(time);
    ScrollTrigger.update();
    requestAnimationFrame(raf);
  }
  requestAnimationFrame(raf);

  lenis.on("scroll", ScrollTrigger.update);
} else {

  console.log("Lenis disabled on mobile");
}
Step 2 - Hero section animations

Animations for headline, subheadline, and background.

	/*======================================================
  MOUSE PARALLAX
  ========================================================*/

  document.addEventListener("mousemove", (e) => {
    const sectionX = (e.clientX / window.innerWidth - 0.5) * 50;
    const sectionY = (e.clientY / window.innerHeight - 0.5) * 30;

    gsap.to(".image-parallax", {
      x: sectionX,
      y: sectionY,
      transformPerspective: 800,
      ease: "power2.out",
      duration: 0.6
    });

    const objX = -(e.clientX / window.innerWidth - 0.5) * 40;
    const objY = -(e.clientY / window.innerHeight - 0.5) * 40;

    gsap.to(".image-hero", {
      x: objX,
      y: objY,
      ease: "power2.out",
      duration: 1
    });
  });
  
  /* =====================================================
     HERO SECTION
  ===================================================== */

  const split = new SplitText(".paragraph-second-hero, .tag-tittle-hero", {type : "lines"});
  gsap.from(split.lines, {
    opacity: 0,
    y: 40,
    filter: "blur(15px)",
    duration: 0.85,
    ease: "power2.out",
    stagger: 0.25 
  });
  
  const paragraph = document.querySelector(".wrapper-paragraph-hero");
const crystal = document.querySelector(".wrapper-image-hero");

if (paragraph && crystal) {

  if (window.matchMedia("(min-width: 768px)").matches) {
    gsap.set(paragraph, { opacity: 0, visibility: "hidden" });

    const split = new SplitText(paragraph, { type: "words,chars" });
    const hoverTimeline = gsap.timeline({ paused: true })
      .from(split.chars, {
        opacity: 0,
        scale: 0,
        filter: "blur(20px)",
        rotateX: 90,
        stagger: { amount: 1, from: "random" },
        ease: "back.out(1.7)",
        duration: 0.4,
        immediateRender: false
      });

    crystal.addEventListener("mouseenter", () => {
      gsap.set(paragraph, { visibility: "visible" });
      gsap.to(paragraph, { opacity: 1, scale:1, filter:"blur(0px)", duration: 0.3 });
      hoverTimeline.play(0);
    });

    crystal.addEventListener("mouseleave", () => {
      gsap.to(paragraph, { 
        opacity: 0, 
        duration: 0.3,
        onComplete: () => gsap.set(paragraph, { visibility: "hidden" })
      });
      hoverTimeline.reverse();
    });

  } else {
    gsap.set(paragraph, { opacity: 1, visibility: "visible" });
    gsap.from(paragraph,{
    opacity : 0,
    y: 20,
    filter: "blur(20px)",
    duration: 0.35
    })
  }
}
	
  //Hover Button
	const btn = document.querySelector('.wrapper-btn-hero');
  btn.addEventListener('mouseenter', () => {
    gsap.to(btn, { 
    duration: 0.4, 
    boxShadow: "0 0 8px #ff3900, 0 0 12px #ff3900", 
    ease: "power2.out" });
  });
  btn.addEventListener('mouseleave', () => {
    gsap.to(btn, { 
    duration: 0.4, 
    boxShadow: "0 0 0px transparent", 
    ease: "power2.in" });
  });
Step 3 - About us section animations

Entrance about our section, with blur and skew animations.

/* =====================================================
     ABOUT SECTION
  ===================================================== */
  const about = document.querySelector(".wrapper-about");
  if (about) {
    about.style.perspective = "1000px";
    about.style.transformStyle = "preserve-3d";
    gsap.from([".text-about-first", ".text-about-second", ".text-about-third"], {
      opacity: 0, 
      scaleY: 0, 
      scaleX: 1.2, 
      skewY: 5, 
      rotateX: 45,
      filter: "blur(10px)", 
      transformOrigin: "center bottom",
      duration: 1.2, 
      ease: "power3.out", 
      stagger: 0.25,
      scrollTrigger: { 
      trigger: ".wrapper-about", 
      start: "top 40%", 
      end: "top 30%", 
      toggleActions: "play none none none" }
    });
  }
Step 4 - Mouse trail effect

Images follow the cursor when moving the mouse.

  /* =====================================================
     MOUSE TRAIL
  ===================================================== */
 
  if (window.matchMedia("(min-width: 768px)").matches) {
    const images = document.querySelectorAll(".trail-img");
    let mouse = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    let pos = { x: mouse.x, y: mouse.y };
    let moveTimeout;
    let isVisible = false; 

   
    gsap.set(images, { x: mouse.x, y: mouse.y, opacity: 0, scale: 0 });

    
    let lastMouse = { x: mouse.x, y: mouse.y };
    const threshold = 15; 


    window.addEventListener("mousemove", (e) => {
      const dx = Math.abs(e.clientX - lastMouse.x);
      const dy = Math.abs(e.clientY - lastMouse.y);

      
      if (dx < threshold && dy < threshold) return;

      mouse.x = e.clientX;
      mouse.y = e.clientY;
      lastMouse.x = mouse.x;
      lastMouse.y = mouse.y;

      if (!isVisible) {
        
        gsap.to(images, { opacity: 1, scale: 1, duration: 0.5, stagger: 0.1 });
        isVisible = true;
      }

      clearTimeout(moveTimeout);
      moveTimeout = setTimeout(() => {
        gsap.to(images, { opacity: 0, scale: 0, duration: 0.8 });
        isVisible = false;
      }, 400); 
    });

    function animateTrail() {
      
      pos.x += (mouse.x - pos.x) * 0.08;
      pos.y += (mouse.y - pos.y) * 0.08;

      if (isVisible) {
        gsap.to(images, {
          x: pos.x,
          y: pos.y,
          duration: 0.8,
          ease: "power3.out",
          stagger: {
            each: 0.15,
            from: "start"
          }
        });
      }

      requestAnimationFrame(animateTrail);
    }

    animateTrail();
  }

Step 5 - Service section animations

Entrance headline, and hover effects.

 /* =====================================================
     SERVICE SECTION
===================================================== */

// TAGLINE ANIMATION
const taglineSplit = getSplit('.tagline-all', 'chars');
gsap.from(taglineSplit.chars, {
  scrollTrigger: { 
    trigger: ".tagline-all", 
    start: "top 80%", 
    toggleActions: "play none none none" 
  },
  scale: 0, 
  filter: "blur(20px)", 
  opacity: 0,
  duration: 0.8, 
  stagger: { each: 0.05, from: "random" }, 
  ease: "back.out(1.7)"
});

// DESCRIPTION
	const lines = document.querySelectorAll(".text-service-description");

gsap.from(lines, {
  scrollTrigger: {
    trigger: ".wrapper-service",
    start: "top 70%",
    toggleActions: "play none none none",
    markers: false
  },
  y: 40,
  opacity: 0,
  filter: "blur(8px)",
  duration: 0.8,
  ease: "power2.out",
  stagger: 0.2 
});

//Start hover
if (window.matchMedia("(min-width: 480px)").matches) {
  document.querySelectorAll(".overflow-hide-image").forEach(overflow => {
    const card = overflow.closest(".tag-jobs");
    const tags = card?.querySelectorAll(".stroke-tag-job");
    const img = card?.querySelector(".image-service");

    gsap.set(tags, { y: -30, opacity: 0 });
    if (img) {
      gsap.set(img, { y: 30, opacity: 0, rotateZ: 0 });
    }

    const tl = gsap.timeline({ paused: true });
    tl.to(tags, { 
      y: 0, 
      opacity: 1, 
      duration: 0.4, 
      stagger: 0.1, 
      ease: "power2.out" 
    });
    
    if (img) {
      tl.to(img, { 
        y: 0, 
        opacity: 1, 
        rotateZ: 15, 
        duration: 0.5, 
        ease: "power2.out" 
      }, "<");
    }

    overflow.addEventListener("mouseenter", () => tl.play());
    overflow.addEventListener("mouseleave", () => tl.reverse());
  });
}
Step 6 - Team section animations

Entrance headline, Card positions and hover effects.

/* =====================================================
     TEAM SECTION
===================================================== */
const teamSplit = getSplit(".subline-team ", "lines");
gsap.from(teamSplit.lines, {
  scrollTrigger: { 
    trigger: ".subline-team", 
    start: "top 80%", 
    toggleActions: "play none none none", 
    once: true 
  },
  y: 40,
  filter: "blur(20px)", 
  opacity: 0,
  duration: 0.8, 
  ease: "power2.out"
});

gsap.from(".inner-text-team > *", {
  scrollTrigger: {
    trigger: ".wrapper-text-team", 
    start: "top 75%",
    toggleActions: "play none none none",
    markers: false
  },
  opacity: 0,
  y: 40,
  filter: "blur(8px)",
  duration: 0.8,
  ease: "power2.out",
  stagger: 0.15 
});

const teamTagSplit = getSplit('.tagline-team', 'chars');
gsap.from(teamTagSplit.chars, {
  scrollTrigger: { 
    trigger: ".tagline-team", 
    start: "top 80%", 
    toggleActions: "play none none reverse" 
  },
  scale: 0, 
  filter: "blur(20px)", 
  opacity: 0,
  duration: 0.8, 
  stagger: { each: 0.05, from: "random" }, 
  ease: "back.out(1.7)"
});

let mm = gsap.matchMedia();

mm.add("(min-width: 768px)", () => {

  const positions = [
    { selector: ".pos-one", props: { x: 40, y: 40, z: -60, rotationY: -25, scale: 0.85, zIndex: 1, filter: "grayscale(100%)" } },
    { selector: ".pos-two", props: { y: 40, rotationY: -15, scale: 0.92, zIndex: 2, filter: "grayscale(100%)" } },
    { selector: ".pos-three", props: { y: 40, rotationY: 0, scale: 1, zIndex: 3, filter: "grayscale(100%)" } },
    { selector: ".pos-four", props: { y: 40, rotationY: 15, scale: 0.92, zIndex: 2, filter: "grayscale(100%)" } },
    { selector: ".pos-five", props: { x: -40, y: 40, z: -60, rotationY: 25, scale: 0.85, zIndex: 1, filter: "grayscale(100%)" } }
  ];

  positions.forEach(pos => {
    const el = document.querySelector(pos.selector);
    if (el) {
      gsap.set(el, pos.props);
      el.dataset.original = JSON.stringify(pos.props); 
    }
  });

  document.querySelectorAll(".pos-one, .pos-two, .pos-three, .pos-four, .pos-five").forEach(card => {
    card.addEventListener("mouseenter", () => {
    if (window.innerWidth < 768) return;
      let animProps = { 
        y: -10, 
        rotationY: 0, 
        scale: 1.05, 
        zIndex: 5, 
        filter: "grayscale(0%)", 
        duration: 0.4, 
        ease: "power2.out" 
      };
      
      if (card.classList.contains("pos-one")) animProps.x = 20;
      if (card.classList.contains("pos-five")) animProps.x = -20;

      gsap.to(card, animProps);
    });

    card.addEventListener("mouseleave", () => {
    if (window.innerWidth < 768) return;
      const original = JSON.parse(card.dataset.original);
      gsap.to(card, { ...original, duration: 0.4, ease: "power2.inOut" });
    });
  });
});
Step 7 - Navbar menu animations

In the animation section of the navbar menu, you need to combine several style classes.

/* =====================================================
     NAVBAR HOVER ANIMATION
  ===================================================== */
  document.querySelectorAll('.navbar-item-text').forEach(el => {
    const letters = el.textContent.trim().split('');
    el.innerHTML = letters.map(l => {
      if (l === " ") {
        return `<span>&nbsp;</span>`; 
      } else {
        return `<span>${l}</span>`;
      }
    }).join('');
  });


  document.querySelectorAll('.navbar-item').forEach(item => {
    const defaultSpans = item.querySelectorAll('.default span');
    const hiddenSpans = item.querySelectorAll('.hidden span');

    
    gsap.set(hiddenSpans, { yPercent: 100, opacity: 0 });

    item.addEventListener('mouseenter', () => {
      gsap.to(defaultSpans, {
        yPercent: -100,
        opacity: 0,
        stagger: 0.05,
        duration: 0.4,
        ease: "power2.out"
      });
      gsap.to(hiddenSpans, {
        yPercent: 0,
        opacity: 1,
        stagger: 0.05,
        duration: 0.4,
        ease: "power2.out"
      });
    });

    item.addEventListener('mouseleave', () => {
      gsap.to(defaultSpans, {
        yPercent: 0,
        opacity: 1,
        stagger: 0.05,
        duration: 0.4,
        ease: "power2.in"
      });
      gsap.to(hiddenSpans, {
        yPercent: 100,
        opacity: 0,
        stagger: 0.05,
        duration: 0.4,
        ease: "power2.in"
      });
    });
  });
Step 8 - Footer section animations

In the animation section of the navbar menu, you need to combine several style classes.

 /* =====================================================
     FOOTER LINKS
  ===================================================== */
  document.querySelectorAll(".text-link").forEach(link => {
    const split = new SplitText(link, { type: "chars" });
    link.split = split;
    const spans = split.chars;
    link.addEventListener("mouseenter", () => {
      gsap.to(spans, { color: "#ff3900", stagger: 0.03, duration: 0.3, ease: "power2.out" });
    });
    link.addEventListener("mouseleave", () => {
      gsap.to(spans, {  color: "#fff", stagger: 0.03, duration: 0.3, ease: "power2.in" });
    });
  });

Notes

Desktop & Tablet only: Features that use cursor movement or hover states.
All devices: Smooth scrolling, text split, and scroll-triggered animations.