Gsap Webflow animations
This template uses Webflow's native GSAP integration built directly into the Designer. All animations are created using Webflow's intuitive Scroll Effects and Interactions panel - no coding required.
Key Features:
- Smooth parallax effects on images
- Scroll-triggered animations
- Interactive hover states
Customization is simple: select any animated element in the Designer, open the Interactions panel, and adjust timing, easing, or trigger points to match your needs.All animations are fully editable through Webflow's visual interface.
Gsap custom code
The template includes two custom GSAP scripts for enhanced functionality that goes beyond Webflow's native capabilities.
Homepage Specific Animations
Global Script (Site-wide)
Located in: Project Settings → Custom Code → Footer Code
1. Navigation Menu
• Smooth burger menu animation with morphing lines
• Dynamic height calculation for menu overlay
• Automatic close on Escape key
• Hover effects on burger icon
2. Button Animations
• Text slide-up effect on hover
• Arrow movement with clone animation
• Works on any button with [gsap-button] attribute
• Automatic arrow clone generation
3. Number Roll Animation
• Animated counting effect for statistics
• Scroll-triggered activation
• Supports suffixes (%, M, K+, etc.)
• Dynamic duration based on number size
• Add [number-roll] attribute to any text element
<script>
/**
* OOTO - Global Script
* Navigation + Buttons + Number Roll
*/
var Webflow = Webflow || [];
Webflow.push(function() {
// --- 1. NAVIGATION LOGIC ---
const burgerBtn = document.querySelector('.burger-btn');
const line1 = document.querySelector('.line-1');
const line2 = document.querySelector('.line-2');
const navWrapper = document.querySelector('.nav_wrapper');
const menuOverlay = document.querySelector('.menu_overlay');
const bgOverlay = document.querySelector('.menu_bg_overlay');
const topBar = document.querySelector('.navbar-content-wrap');
if (burgerBtn && navWrapper && line1 && line2 && menuOverlay && bgOverlay && topBar) {
const body = document.body;
const getTargetWidth = () => {
if (window.innerWidth <= 991) {
return window.innerWidth <= 500 ? '100%' : '500px';
}
return '660px';
};
// Initial state
gsap.set(navWrapper, { width: getTargetWidth(), height: '57px', overflow: 'hidden' });
gsap.set(menuOverlay, { opacity: 0, display: 'none' });
gsap.set(bgOverlay, { display: 'none', opacity: 0 });
const menuTimeline = gsap.timeline({
paused: true,
reversed: true,
defaults: { ease: "expo.inOut", duration: 0.8 },
onStart: () => {
gsap.set([bgOverlay, menuOverlay], { display: 'block' });
body.style.overflow = 'hidden';
},
onReverseComplete: () => {
gsap.set([bgOverlay, menuOverlay], { display: 'none' });
gsap.set(navWrapper, { width: getTargetWidth(), height: '57px' });
body.style.overflow = 'auto';
gsap.to(line2, { width: "30px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "transparent", duration: 0.4 });
}
});
menuTimeline
.to(bgOverlay, { opacity: 1, duration: 0.3 }, 0)
.to(navWrapper, { width: '100%' }, 0)
.to(burgerBtn, { backgroundColor: "#363636", duration: 0.4 }, 0)
.to(line1, { y: 5.25, duration: 0.7 }, 0)
.to(line2, { y: -5.25, width: "42px", duration: 0.7 }, 0)
.to(menuOverlay, { opacity: 1, duration: 0.5 }, 0.8);
function toggleMenu() {
if (!menuTimeline.isActive()) {
if (menuTimeline.reversed()) {
// Calculate dynamic height for opening
gsap.set(menuOverlay, { display: 'block', visibility: 'hidden', opacity: 1 });
const finalH = topBar.offsetHeight + menuOverlay.offsetHeight;
gsap.set(menuOverlay, { display: 'none', visibility: 'visible', opacity: 0 });
menuTimeline.to(navWrapper, { height: finalH + "px" }, 0.7);
menuTimeline.play();
} else {
menuTimeline.reverse();
}
burgerBtn.classList.toggle('is-active');
}
}
burgerBtn.addEventListener('click', toggleMenu);
bgOverlay.addEventListener('click', toggleMenu);
// Burger hover effect
burgerBtn.addEventListener('mouseenter', () => {
if (menuTimeline.reversed()) {
gsap.to(line2, { width: "42px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "#363636", duration: 0.4 });
}
});
burgerBtn.addEventListener('mouseleave', () => {
if (menuTimeline.reversed()) {
gsap.to(line2, { width: "30px", duration: 0.4 });
gsap.to(burgerBtn, { backgroundColor: "transparent", duration: 0.4 });
}
});
window.addEventListener('keydown', (e) => {
if ((e.key === "Escape") && !menuTimeline.reversed()) toggleMenu();
});
}
// --- 2. BUTTONS LOGIC (Manual Webflow Structure) ---
const buttons = document.querySelectorAll('[gsap-button]');
buttons.forEach((btn) => {
const orig = btn.querySelector('.text-orig');
const clone = btn.querySelector('.text-clone');
const arrow = btn.querySelector('.arrow-btn');
const arrowWrapp = btn.querySelector('.btn-arrow-wrap');
// Handle arrow clone (if not present in DOM)
if (arrowWrapp && arrow && !arrowWrapp.querySelector('.arrow-clone')) {
const arrowClone = arrow.cloneNode(true);
arrowClone.classList.add('arrow-clone');
gsap.set(arrowClone, { position: "absolute", top: "50%", left: "50%", xPercent: -200, yPercent: -50, opacity: 0 });
arrowWrapp.appendChild(arrowClone);
}
btn.addEventListener('mouseenter', () => {
const aClone = btn.querySelector('.arrow-clone');
gsap.to([orig, clone], { yPercent: -100, duration: 0.4, ease: "power2.inOut", overwrite: true });
if(arrow && aClone) {
gsap.to(arrow, { xPercent: 200, opacity: 0, duration: 0.4, ease: "power2.inOut" });
gsap.to(aClone, { xPercent: -50, opacity: 1, duration: 0.4, ease: "power2.inOut" });
}
});
btn.addEventListener('mouseleave', () => {
const aClone = btn.querySelector('.arrow-clone');
gsap.to([orig, clone], { yPercent: 0, duration: 0.4, ease: "power2.inOut", overwrite: true });
if(arrow && aClone) {
gsap.to(arrow, { xPercent: 0, opacity: 1, duration: 0.4, ease: "power2.inOut" });
gsap.to(aClone, { xPercent: -200, opacity: 0, duration: 0.4, ease: "power2.inOut" });
}
});
});
// --- 3. NUMBER ROLL ANIMATION (Global - usable on all pages) ---
const numberElements = document.querySelectorAll('[number-roll]');
numberElements.forEach((el) => {
const rawText = el.innerText.trim();
// Extract only the number
const targetValue = parseFloat(rawText.replace(/[^0-9.]/g, '')) || 0;
// Extract the suffix (M, %, etc.)
const suffix = rawText.replace(/[0-9.]/g, '');
let countObj = { value: 0 };
// Dynamic duration between 1.5s and 3.5s depending on number magnitude
const dynamicDuration = Math.min(Math.max(targetValue / 100, 1.5), 3.5);
gsap.to(countObj, {
value: targetValue,
duration: dynamicDuration,
ease: "expo.out",
scrollTrigger: {
trigger: el,
start: "top 90%",
toggleActions: "play none none none"
},
onUpdate: function() {
// Display raw number rounded down to nearest integer
el.innerText = Math.floor(countObj.value) + suffix;
},
onComplete: function() {
// Ensure final value is exact
el.innerText = targetValue + suffix;
}
});
});
});
</script>Homepage Specific Animations
Homepage Script
Located in: Homepage → Page Settings → Custom Code → Before </body> tag
This script creates the unique visual effects on the homepage.
Typewriter Effect
• Animated code typing
• Infinite loop with erase/rewrite cycle
• Smooth character-by-character animation
• Applied to elements with .typewriting-text class.
Floating Circles (Parallax Background)
• Interactive parallax circles that follow mouse movement
• Smooth floating animation with random delays
• Entry animation on page load
• Customizable speed via data attributes:
- data-parallax-speed: Controls mouse follow intensity
- data-float-amplitude: Controls floating movement range
<script>
/**
* OOTO - Homepage: Circles Animation + Typewriter Effect
* Handles parallax, floating circles, and code typing animation
*/
var Webflow = Webflow || [];
Webflow.push(function() {
// --- 1. TYPEWRITING ANIMATION (Homepage specific) ---
const codeContainer = document.querySelector(".typewriting-text");
if (codeContainer) {
const codeText = `fetch('https://api.ooto.io/v1/deploy', {
method: 'POST',
headers: {
'Authorization': 'Bearer OOTO_SK_7742',
'Content-Type': 'application/json'
},
body: JSON.stringify({
modelName: 'neural-network-v2.1',
clusterId: 'us-east-cluster-01',
clusterType: 'gpu.t4',
scaleReplicas: 5
})
})`;
gsap.to(codeContainer, {
duration: 5,
text: {
value: codeText,
delimiter: "",
preserveSpaces: true
},
ease: "none",
repeat: -1,
yoyo: true,
repeatDelay: 2,
holdMs: 2000,
scrollTrigger: {
trigger: codeContainer,
start: "top 85%"
}
});
}
// --- 2. BACKGROUND CIRCLES (Parallax & Floating) ---
const circles = document.querySelectorAll('.circle-element');
if (circles.length > 0) {
let mouseX = 0, mouseY = 0;
// Entry animation
gsap.to(circles, {
scale: 1,
opacity: 1,
duration: 2,
ease: "elastic.out(1, 0.75)",
stagger: 0.1,
delay: 0.2
});
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX - window.innerWidth / 2;
mouseY = e.clientY - window.innerHeight / 2;
});
circles.forEach((circle) => {
const speed = parseFloat(circle.getAttribute('data-parallax-speed')) || 0.05;
const amp = parseFloat(circle.getAttribute('data-float-amplitude')) || 2;
circle._parallaxPos = { x: 0, y: 0 };
circle._floatPos = { x: 0, y: 0 };
// Infinite floating loop
gsap.to(circle._floatPos, {
x: amp, y: amp,
duration: gsap.utils.random(3, 5),
ease: "sine.inOut",
repeat: -1,
yoyo: true,
delay: gsap.utils.random(0, 2)
});
// Render loop (Ticker) to combine movements
gsap.ticker.add(() => {
circle._parallaxPos.x += (mouseX * speed - circle._parallaxPos.x) * 0.1;
circle._parallaxPos.y += (mouseY * speed - circle._parallaxPos.y) * 0.1;
const floatX = circle._floatPos.x * (window.innerWidth / 100);
const floatY = circle._floatPos.y * (window.innerHeight / 100);
gsap.set(circle, {
x: circle._parallaxPos.x + floatX,
y: circle._parallaxPos.y + floatY
});
});
});
}
});
</script>
<style>
.typewriting-text::after {
content: "|";
animation: blink 1s step-end infinite;
}
@keyframes blink {
from, to { color: transparent; }
50% { color: red; }
}
</style>