Instructions
This template uses GSAP (GreenSock Animation Platform) to create smooth, high-performance animations across multiple sections of the website. All GSAP scripts are written in pure JavaScript and organized for easy customization, allowing you to adjust animation speed, direction, easing, triggers, and timing without affecting the overall structure.
Each animation includes clear comments to help you understand how it works, making it simple to modify or extend the interactions to match your project's needs while remaining fully compatible with Webflow.
Toggle Navbar Animation
This script creates a smooth, high-performance menu icon toggle animation using GSAP. It dynamically transitions a standard hamburger menu icon into a sleek close ("X") icon upon user click, providing a seamless interactive navigation experience with synchronized state updates.
Key Features
Pure GSAP Timeline Controls: Leverages GSAP’s timeline and native state checking (.play(), .reverse(), .reversed()) for smooth bidirectional playback without manual boolean flags.
Staggered Line Animations: Morphing effect features staggered width and scale transitions for the hamburger lines, giving it a fluid micro-interaction feel.
Synchronized CSS State: Automatically syncs the .is-active class via GSAP lifecycle callbacks (onPlay & onReverseComplete) to manage overlay or layout state seamlessly.
Pure GSAP Timeline Controls: Leverages GSAP’s timeline and native state checking (.play(), .reverse(), .reversed()) for smooth bidirectional playback without manual boolean flags.
Staggered Line Animations: Morphing effect features staggered width and scale transitions for the hamburger lines, giving it a fluid micro-interaction feel.
Synchronized CSS State: Automatically syncs the .is-active class via GSAP lifecycle callbacks (onPlay & onReverseComplete) to manage overlay or layout state seamlessly.
<script>
document.addEventListener("DOMContentLoaded", () => {
// Check GSAP availability
if (typeof gsap === "undefined") return;
// Select menu elements
const menuBtn = document.querySelector(".menu-button");
if (!menuBtn) return;
const burgerIcon = menuBtn.querySelector(".burger-icon");
const burgerLines = menuBtn.querySelectorAll(".menu-icon-burger");
const closeLines = menuBtn.querySelectorAll(".menu-icon-line");
if (!burgerIcon || burgerLines.length < 3 || closeLines.length < 3) return;
const [closeTop, closeMiddle, closeBottom] = closeLines;
// Set initial properties
gsap.set(closeLines, { transformOrigin: "50% 50%" });
// GSAP Menu Timeline
const burgerTL = gsap.timeline({
paused: true,
defaults: { duration: 0.45, ease: "power4.inOut" },
onPlay: () => menuBtn.classList.add("is-active"),
onReverseComplete: () => menuBtn.classList.remove("is-active")
});
burgerTL
.to(burgerLines, { width: "0%", stagger: 0.05, transformOrigin: "100% 50%" }, 0)
.to(burgerIcon, { opacity: 0, scale: 0.8, transformOrigin: "100% 50%" }, 0)
.to(closeMiddle, { width: "0%", opacity: 0 }, 0)
.to(closeTop, { width: "100%", rotate: 135 }, 0.1)
.to(closeBottom, { width: "100%", rotate: 45 }, 0.1);
// Toggle animation on click
menuBtn.addEventListener("click", () => {
if (burgerTL.reversed() || (burgerTL.paused() && burgerTL.progress() === 0)) {
burgerTL.play();
} else {
burgerTL.reverse();
}
});
});
</script>Hover Image
This script creates a premium micro-interaction for image cards (commonly used in Hero, Portfolio, or CMS Collection sections). When a user hovers over the designated image container, an interactive custom button smoothly appears and tracks the cursor dynamically (mouse trail effect), while the targeted image applies a subtle, elegant zoom effect without affecting sibling elements.
Key Features:
Custom Attribute Architecture: Driven 100% by custom attributes (
Isolated Image Zoom: Zooms only the specific hovered image (
Scoped Cursor Tracking: Dynamically tracks mouse movement (
Elastic Click Feedback: Plays a quick elastic bounce effect (
Desktop-Only Execution: Restricts cursor tracking and hover animations strictly to desktop viewports (> 991px) to preserve touch device performance and usability.
Custom Attribute Architecture: Driven 100% by custom attributes (
hover-element="image" and hover-element="button"), making it completely independent of CSS class names and fully compatible with Webflow CMS Collection lists.Isolated Image Zoom: Zooms only the specific hovered image (
scale: 1.08) while keeping its overflow clipped neatly inside the parent container (overflow: hidden).Scoped Cursor Tracking: Dynamically tracks mouse movement (
power3.out) for the custom tag/button relative strictly to its parent container or nearest CMS item, preventing cross-card target jumping.Elastic Click Feedback: Plays a quick elastic bounce effect (
elastic.out) on the custom button when clicked for enhanced interactive responsiveness.Desktop-Only Execution: Restricts cursor tracking and hover animations strictly to desktop viewports (> 991px) to preserve touch device performance and usability.
<script>
document.addEventListener("DOMContentLoaded", () => {
// Check desktop viewport and GSAP availability
const isDesktop = !window.matchMedia("(max-width: 991px)").matches;
if (!isDesktop || typeof gsap === "undefined") return;
// Select all image elements with the custom attribute
const images = document.querySelectorAll('[hover-element="image"]');
images.forEach((img) => {
// The hover area is the direct parent of the image
const hoverArea = img.parentElement;
if (!hoverArea) return;
// Look for a custom button ONLY inside the parent wrapper
let tag = hoverArea.querySelector('[hover-element="button"]');
// If not found in direct parent, check nearest CMS item container
if (!tag) {
const cmsItem = img.closest('.w-dyn-item');
if (cmsItem) {
tag = cmsItem.querySelector('[hover-element="button"]');
}
}
// Ensure image zoom stays contained within its parent wrapper
gsap.set(hoverArea, { overflow: "hidden" });
// Set initial properties ONLY if a button exists for this specific image
if (tag) {
gsap.set(tag, {
opacity: 0,
scale: 0,
xPercent: -50,
yPercent: -50,
position: "absolute",
pointerEvents: "none"
});
}
// --- EVENT LISTENERS ---
// Mouse Enter: Zoom the specific image & reveal tag (if present)
hoverArea.addEventListener("mouseenter", () => {
gsap.to(img, { scale: 1.08, duration: 0.5, ease: "power2.out" });
if (tag) {
gsap.to(tag, { opacity: 1, scale: 1, duration: 0.3, ease: "back.out(1.5)" });
}
});
// Mouse Leave: Reset image zoom & hide tag (if present)
hoverArea.addEventListener("mouseleave", () => {
gsap.to(img, { scale: 1, duration: 0.5, ease: "power2.out" });
if (tag) {
gsap.to(tag, { opacity: 0, scale: 0, duration: 0.25, ease: "power2.in" });
}
});
// Mouse Move: Move tag relative to the hover area (if present)
hoverArea.addEventListener("mousemove", (e) => {
if (!tag) return;
const rect = hoverArea.getBoundingClientRect();
gsap.to(tag, {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
duration: 0.35,
ease: "power3.out",
overwrite: "auto"
});
});
// Click Effect: Elastic bounce animation on click (if present)
hoverArea.addEventListener("click", () => {
if (!tag) return;
gsap.fromTo(tag,
{ scale: 0.8 },
{ scale: 1, duration: 0.45, ease: "elastic.out(1.2, 0.4)" }
);
});
});
});
</script>Number Counter Animation
This script enhances website interactivity and visual appeal by triggering smooth number counter entrance animations as elements scroll into view. Built on pure GSAP and ScrollTrigger, it seamlessly combines a staggered fade-in reveal for content blocks with a dynamic numerical count-up effect, delivering a high-performance and SEO-friendly user experience.
Key Features
Pure GSAP ScrollTrigger: Built entirely with GSAP’s ScrollTrigger API to ensure smooth, hardware-accelerated animations upon entering the viewport without relying on extra browser observers.
Dynamic Animated Counter: Automatically detects target numbers and suffixes (e.g.,
One-Time Trigger Efficiency: Utilizes GSAP’s
Pure GSAP ScrollTrigger: Built entirely with GSAP’s ScrollTrigger API to ensure smooth, hardware-accelerated animations upon entering the viewport without relying on extra browser observers.
Dynamic Animated Counter: Automatically detects target numbers and suffixes (e.g.,
%, +, k) within text elements and smoothly counts up from zero using a natural ease-out curve.One-Time Trigger Efficiency: Utilizes GSAP’s
once: true configuration to destroy scroll listeners immediately after playing, saving memory and optimizing overall page performance.<script>
document.addEventListener("DOMContentLoaded", () => {
// Check GSAP and ScrollTrigger availability
if (typeof gsap === "undefined" || typeof ScrollTrigger === "undefined") return;
gsap.registerPlugin(ScrollTrigger);
// Animated Number Counter
const counterElements = document.querySelectorAll('[scroll-element="number"]');
counterElements.forEach((element) => {
const rawText = element.textContent.trim();
const targetNumber = parseInt(rawText.replace(/[^\d]/g, ""), 10);
const suffix = rawText.replace(/[\d]/g, "");
if (isNaN(targetNumber)) return;
const counterObj = { value: 0 };
ScrollTrigger.create({
trigger: element,
start: "top 90%",
once: true,
onEnter: () => {
gsap.to(counterObj, {
value: targetNumber,
duration: 1.8,
ease: "power2.out",
onUpdate: () => {
element.textContent = Math.floor(counterObj.value) + suffix;
},
onComplete: () => {
element.textContent = rawText;
}
});
}
});
});
});
</script>FAQ Accordion
This script provides a smooth and intuitive interactive FAQ accordion using jQuery. When a user clicks on an FAQ item, the corresponding answer expands or collapses with a sliding animation (slideUp / slideDown), while state icons (open/close) transition dynamically using a fade effect. It also automatically collapses any other open FAQ answers when a new one is clicked, keeping the page layout clean, organized, and space-efficient.
Key Features
Smooth Slide Toggle: Expands and collapses FAQ responses smoothly using
Dynamic Icon Transition: Automatically toggles indicator icons (
Auto-Collapse Accordion: Automatically closes all other open answers and resets their corresponding icons whenever a new FAQ item is opened, ensuring only one answer is active at a time.
Initial Hidden State: Automatically hides all answer containers (
Smooth Slide Toggle: Expands and collapses FAQ responses smoothly using
slideDown and slideUp animations with a 300ms duration.
Dynamic Icon Transition: Automatically toggles indicator icons (
icon-open and icon-close) using fadeIn and fadeOut effects to provide clear visual feedback to the user.
Auto-Collapse Accordion: Automatically closes all other open answers and resets their corresponding icons whenever a new FAQ item is opened, ensuring only one answer is active at a time.
Initial Hidden State: Automatically hides all answer containers (
answer-wrap) and close icons (icon-close) as soon as the page finishes loading.<script>
document.addEventListener("DOMContentLoaded", () => {
// Check GSAP availability
if (typeof gsap === "undefined") return;
const faqItems = document.querySelectorAll(".faq-details");
if (!faqItems.length) return;
// Set initial hidden states safely via GSAP
faqItems.forEach((item) => {
const answer = item.querySelector(".answer-wrap");
const iconClose = item.querySelector(".icon-close");
if (answer) gsap.set(answer, { height: 0, opacity: 0, display: "none", overflow: "hidden" });
if (iconClose) gsap.set(iconClose, { opacity: 0, display: "none" });
});
faqItems.forEach((accordion) => {
accordion.addEventListener("click", function () {
const currentAnswer = this.querySelector(".answer-wrap");
const currentIconOpen = this.querySelector(".icon-open");
const currentIconClose = this.querySelector(".icon-close");
if (!currentAnswer) return;
const isOpen = currentAnswer.classList.contains("is-open");
// Auto-collapse all other FAQ items
faqItems.forEach((item) => {
const answer = item.querySelector(".answer-wrap");
const iconOpen = item.querySelector(".icon-open");
const iconClose = item.querySelector(".icon-close");
if (answer && answer.classList.contains("is-open")) {
answer.classList.remove("is-open");
gsap.to(answer, {
height: 0,
opacity: 0,
duration: 0.3,
ease: "power2.inOut",
onComplete: () => gsap.set(answer, { display: "none" })
});
if (iconClose) gsap.to(iconClose, { opacity: 0, duration: 0.2, onComplete: () => gsap.set(iconClose, { display: "none" }) });
if (iconOpen) {
gsap.set(iconOpen, { display: "block" });
gsap.to(iconOpen, { opacity: 1, duration: 0.2 });
}
}
});
// Expand clicked item if it was closed
if (!isOpen) {
currentAnswer.classList.add("is-open");
gsap.set(currentAnswer, { display: "block" });
gsap.to(currentAnswer, {
height: "auto",
opacity: 1,
duration: 0.35,
ease: "power2.out"
});
if (currentIconOpen) {
gsap.to(currentIconOpen, { opacity: 0, duration: 0.2, onComplete: () => gsap.set(currentIconOpen, { display: "none" }) });
}
if (currentIconClose) {
gsap.set(currentIconClose, { display: "block" });
gsap.to(currentIconClose, { opacity: 1, duration: 0.2 });
}
}
});
});
});
</script>