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.
GSAP & Lenis Script Documentation
This template includes three custom JavaScript modules powered by GSAP and Lenis to create smooth interactions and high-performance animations across your site.
1. Lenis Smooth Scroll
<!-- =========================================================
LENIS SMOOTH SCROLL
---------------------------------------------------------
Lenis handles smooth scrolling and is synchronized
with GSAP ScrollTrigger.
========================================================= -->
<script src="https://unpkg.com/lenis@1.3.4/dist/lenis.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/lenis@1.3.4/dist/lenis.css"
/>
<script>
// Initialize Lenis smooth scrolling
const lenis = new Lenis({
smooth: true,
lerp: 0.1,
wheelMultiplier: 0.75,
infinite: false,
});
// Keep ScrollTrigger synchronized with Lenis
lenis.on("scroll", ScrollTrigger.update);
// Run Lenis through the GSAP ticker
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
// Disable GSAP lag smoothing for consistent scroll synchronization
gsap.ticker.lagSmoothing(0);
</script>A. Overview & Description
This script integrates Lenis, a modern smooth-scrolling library, with GSAP (GreenSock Animation Platform) and ScrollTrigger. It provides an ultra-smooth, high-performance scrolling experience while ensuring all scroll-driven animations stay perfectly synchronized. By routing Lenis's requestAnimationFrame (raf) through GSAP's internal ticker and disabling lag smoothing, the setup prevents jitter and maintains precise scroll position tracking across all devices.
Key Features:
- Smooth Scrolling: Delivers continuous, fluid scrolling using inertia (lerp: 0.1).
- GSAP Synchronization: Keeps ScrollTrigger updates aligned with Lenis's custom scroll frame.
- Lag Smoothing Override: Eliminates animation jumps during sudden frame drops or window re-focusing.
B. How to Edit GSAP Animations
1) Element Map
Below is a reference guide mapping the core JavaScript objects and selectors initialized in this script:
Key Features:
- lenis – Initializes the Lenis smooth-scroll instance with configuration settings (lerp: 0.1, wheelMultiplier: 0.75, infinite: false).
- ScrollTrigger.update – Synchronizes GSAP's ScrollTrigger with Lenis every time a scroll event occurs (lenis.on("scroll", ...)).
- gsap.ticker – Drives Lenis frame updates (lenis.raf) directly within GSAP's render loop and disables lag smoothing (gsap.ticker.lagSmoothing(0)).
2) Customizing Key Variables
You can adjust the smooth scroll performance and GSAP integration behavior directly in the script using these key parameters:
- Lenis Smooth Scroll Settings:
const lenis = new Lenis({ smooth: true, // Enables/disables smooth scrolling lerp: 0.1, // Scroll interpolation/smoothness (lower values = smoother/slower catch-up) wheelMultiplier: 0.75, // Mouse wheel scroll speed multiplier infinite: false, // Enables or disables infinite looping scroll }); - GSAP Ticker & Lag Smoothing:
// Disables GSAP's lag smoothing to ensure GSAP and Lenis tick on the exact same frame gsap.ticker.lagSmoothing(0);
3) Removing GSAP Animations
If you want to modify or remove the GSAP synchronization while keeping Lenis smooth scroll, follow these steps:
a) Step-by-Step Disable Instructions:
- To disable GSAP synchronization entirely while keeping basic Lenis smooth scrolling, remove or comment out the GSAP integration lines:
// Remove or comment out these lines: // lenis.on("scroll", ScrollTrigger.update); // gsap.ticker.add((time) => { lenis.raf(time * 1000); }); // gsap.ticker.lagSmoothing(0); - Replace the ticker update with standard requestAnimationFrame logic to keep Lenis running independently:
function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf);
b) Visual Side Effects & Considerations:
- Scroll Trigger Desync: Disabling ScrollTrigger.update or removing the GSAP ticker sync will cause scroll-based GSAP animations to jitter, lag, or fail to sync accurately with the smooth scroll position.
- Performance Shifts: Removing gsap.ticker.lagSmoothing(0) returns GSAP to its default frame-skipping behavior during heavy page loads, which can cause subtle jumps in scroll animations.
2. Number Counting
<!-- =========================================================
GSAP NUMBER COUNTING
---------------------------------------------------------
Target:
Elements with the ".is-counting" class
Features:
- Counts numbers from 0 to the original value
- Supports prefixes and suffixes
- Supports integer and decimal values
- Plays when ".is-counting" is added
- Reverses when ".is-counting" is removed
- Works with Webflow Interactions through MutationObserver
========================================================= -->
<script>
window.addEventListener("DOMContentLoaded", () => {
gsap.registerPlugin(ScrollTrigger);
// Store each counting tween for later control
const countingTweens = new Map();
// Initialize the counting animation for a single element
function initCounting(element) {
// Prevent duplicate initialization
if (element.dataset.countingInitialized) return;
element.dataset.countingInitialized = "true";
// Read the original text content
const text = element.textContent.trim();
// Extract prefix, numeric value, and suffix
const match = text.match(/^([^\d]*)([\d.]+)(.*)$/);
if (!match) return;
const prefix = match[1] || "";
const value = parseFloat(match[2]);
const suffix = match[3] || "";
// Starting value for the counter
const counter = {
value: 0,
};
// Create the counting tween
const tween = gsap.to(counter, {
value: value,
duration: 1.8,
ease: "power2.out",
paused: true,
// Snap integers to whole numbers and decimals to one decimal place
snap: {
value: Number.isInteger(value) ? 1 : 0.1,
},
// Update the displayed number on every frame
onUpdate() {
const currentValue = Number.isInteger(value)
? Math.round(counter.value)
: counter.value.toFixed(1);
element.textContent = `${prefix}${currentValue}${suffix}`;
},
});
// Trigger the counter when the element enters the viewport
ScrollTrigger.create({
trigger: element,
start: "top 100%",
toggleActions: "play none play reverse",
animation: tween,
});
// Store the tween for manual play/reverse control
countingTweens.set(element, tween);
}
// Initialize elements that already have ".is-counting"
document.querySelectorAll(".is-counting").forEach(initCounting);
// Observe class changes caused by Webflow Interactions
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
const target = mutation.target;
// Start counting when ".is-counting" is added
if (target.classList.contains("is-counting")) {
initCounting(target);
const tween = countingTweens.get(target);
if (tween) {
tween.play();
}
}
// Reverse counting when ".is-counting" is removed
else {
const tween = countingTweens.get(target);
if (tween) {
tween.reverse();
}
}
}
});
});
// Monitor class changes throughout the entire document
observer.observe(document.body, {
attributes: true,
attributeFilter: ["class"],
subtree: true,
});
});
</script>A. Overview & Description
This script creates animated numerical counters using GSAP and ScrollTrigger. It targets any element with the .is-counting class and animates its text value from 0 up to its original number when scrolled into view. Additionally, it integrates a MutationObserver to watch for class changes dynamically, allowing Webflow Interactions to toggle or reverse the counting animation seamlessly.
Key Features:
- Automatic Number Parsing: Detects integers, decimals, prefixes (e.g., $, +), and suffixes (e.g., %, k+) directly from the text content.
- Scroll-Triggered Playback: Animates numbers as soon as they enter the viewport (start: "top 100%").
- Webflow Interaction Support: Replays or reverses counting when the .is-counting class is dynamically added or removed.
B. How to Edit GSAP Animations
1) Element Map
Below is a reference guide mapping the core selectors and functionality initialized in this script:
- .is-counting – The target CSS class applied to text elements that should animate from 0 to their designated number.
- Triggering via Webflow Interactions Timeline: Play / Start Counter: Add the is-counting class to the target element in your interaction timeline. Reverse / Reset Counter: Remove the is-counting class from the target element in your interaction timeline.
- countingTweens – A JavaScript Map that stores individual tween references to allow independent play and reverse controls per element.
- MutationObserver – Monitors document.body for class attribute changes to sync animations with dynamic Webflow interactions.
2) Customizing Key Variables
You can customize the counter speed, easing, and scroll sensitivity directly inside the initCounting() function:
- Duration & Easing:
const tween = gsap.to(counter, { value: value, duration: 1.8, // Animation length in seconds ease: "power2.out", // Acceleration curve (e.g., "power1.out", "expo.out") paused: true, // Snapping configuration for whole numbers vs decimals snap: { value: Number.isInteger(value) ? 1 : 0.1, }, // ... }); - ScrollTrigger Settings:
ScrollTrigger.create({ trigger: element, start: "top 100%", // Triggers as soon as the element hits the bottom of the screen toggleActions: "play none play reverse", // Controls play/reverse behavior on scroll entry/exit animation: tween, });
3) Removing GSAP Animations
If you want to disable or remove the number counting animation, follow these steps:
a) Step-by-Step Disable Instructions:
- Locate and remove or comment out the <script> block containing the GSAP Number Counting code.
- Remove the .is-counting combo class from your Webflow text elements or Webflow Interaction triggers if no longer needed.
b) Visual Side Effects & Considerations:
- Static Display: Removing the script causes numbers to display as static text (e.g., "100%", "$250") immediately upon page load without counting up.
- No Layout Shifts: Because the script reads original text node values before animating, removing it will not break layout dimensions or styling.
3. 3D Ring Carousel
<!-- =========================================================
GSAP 3D RING CAROUSEL (AUTO-ROTATE & DRAG)
---------------------------------------------------------
Target:
- Container area: ".hero-content-image"
- Rotating element: ".image-ring-speed"
Features:
- Continuous 360-degree continuous auto-rotation via requestAnimationFrame
- Interactive horizontal drag-to-rotate using GSAP Draggable
- Smooth auto-pause during user interaction and auto-resume on release
- Optimized performance with gsap.quickSetter
- Custom drag sensitivity and auto-rotation speed configuration
========================================================= -->
<script>
(function () {
'use strict';
const initRingCarousel = () => {
const dragArea = document.querySelector('.hero-content-image');
const ring = document.querySelector('.image-ring-speed');
if (!dragArea || !ring) return;
// Ensure native Webflow interactions on this ring element are disabled
// to prevent conflicting animation values with GSAP.
gsap.registerPlugin(Draggable);
// --- Configuration ---
const DEGREES_PER_SECOND = 360 / 50; // Auto-rotation speed (1 full rotation / 50 seconds)
const DRAG_SENSITIVITY = 0.4; // Cursor drag sensitivity (0.1 - 0.9)
// --- State ---
let currentRot = 0; // Manual rotation value
let lastTime = performance.now();
let isInteracting = false;
// quickSetter for optimal performance
const setRotation = gsap.quickSetter(
ring,
'rotationY',
'deg'
);
// --- 1. Auto-Rotation Loop (Custom requestAnimationFrame) ---
function animationLoop(now) {
const deltaTime = (now - lastTime) / 1000; // seconds
lastTime = now;
if (!isInteracting) {
currentRot += DEGREES_PER_SECOND * deltaTime;
setRotation(currentRot);
}
// Keep running the loop to update time continuously
requestAnimationFrame(animationLoop);
}
requestAnimationFrame(animationLoop);
// --- 2. Setup Draggable in Hero Area ---
Draggable.create(document.createElement('div'), {
type: 'x',
trigger: dragArea,
minimumMovement: 1,
onPress: function () {
isInteracting = true; // Pause auto-rotation
dragArea.style.cursor = 'grabbing';
},
onDrag: function () {
// Add drag movement delta to current rotation value
currentRot -= this.deltaX * DRAG_SENSITIVITY;
setRotation(currentRot);
},
onRelease: function () {
isInteracting = false; // Resume auto-rotation
dragArea.style.cursor = 'grab';
},
});
dragArea.style.cursor = 'grab';
};
// Ensure DOM is fully loaded
if (document.readyState === 'loading') {
document.addEventListener(
'DOMContentLoaded',
initRingCarousel
);
} else {
initRingCarousel();
}
})();
</script>A. Overview & Description
This script powers an interactive 3D Ring Carousel using GSAP Draggable and high-performance requestAnimationFrame rendering. It creates a continuous 360-degree auto-rotating 3D ring element that users can manually drag to rotate horizontally. The auto-rotation smoothly pauses during user interaction and automatically resumes upon release, utilizing gsap.quickSetter for optimal rendering performance.
Key Features:
- 360° Continuous Auto-Rotation: Rotates seamlessly using frame-rate independent calculation (deltaTime).
- Interactive Drag Controls: Implements horizontal drag interaction via Draggable.create without affecting the DOM structure.
- Smart Interaction Pause: Suspends auto-rotation while dragging and updates cursor states (grab / grabbing).
- Performance Optimization: Uses gsap.quickSetter to update CSS rotationY directly on GPU-accelerated layers.
B. How to Edit GSAP Animations
1) Element Map
Below is a reference guide mapping the target selectors and core controls used in this script:
- .hero-content-image – The container element that acts as the drag trigger area.
- .image-ring-speed – The 3D element/ring that performs the rotationY animation.
- Draggable – The GSAP plugin used to capture horizontal drag gestures over the trigger element.
2) Customizing Key Variables
You can adjust rotation speeds, sensitivity, and interaction behaviors directly in the configuration section of the script:
- Auto-Rotation Speed & Drag Sensitivity:
// Auto-rotation speed (360 degrees / total seconds per full turn) const DEGREES_PER_SECOND = 360 / 50; // Takes 50 seconds for 1 full rotation // Cursor drag sensitivity (Recommended range: 0.1 to 0.9) const DRAG_SENSITIVITY = 0.4; - Target Selectors & Cursors:
// Target class selectors const dragArea = document.querySelector('.hero-content-image'); const ring = document.querySelector('.image-ring-speed'); // Drag interaction cursors dragArea.style.cursor = 'grab'; // Idle state dragArea.style.cursor = 'grabbing'; // Active drag state
3) Removing GSAP Animations
If you want to disable or remove the 3D Ring Carousel script, follow these steps:
a) Step-by-Step Disable Instructions:
- Locate and remove or comment out the <script> block containing the 3D Ring Carousel code.
- If you are loading the GSAP Draggable plugin script solely for this component, you can safely remove its script CDN tag as well.
b) Visual Side Effects & Considerations:
- Static Ring Position: Removing the script will stop both auto-rotation and drag controls, leaving .image-ring-speed static at its initial CSS orientation.
- Webflow Interactions Conflict: Ensure native Webflow 3D transforms or hover interactions on .image-ring-speed are re-enabled if you choose to animate the ring via Webflow Interactions instead.
