This guide helps non-tech users utilize GSAP for animations like text splitting and sliders, focusing on documentation without altering code logic.
This sets up GSAP basics.
Elements with data-gsap="split-text", containing plain text.
// ============================================
// GSAP PLUGIN REGISTRATION
// ============================================
gsap.registerPlugin(ScrollTrigger);
gsap.config({ nullTargetWarn: false });
No direct elements; global setup for ScrollTrigger (scroll-based animations) and config to ignore missing targets.
This animates a hero slider with slides, thumbnails, arrows, and autoplay.
// ============================================
// HOME HERO SLIDER WITH GSAP
// ============================================
function initHeroSliderAnimations() {
document.querySelectorAll(".section_home_hero").forEach(section => {
const heroSliderWrap = section.querySelector(".home_hero_slider");
if (!heroSliderWrap) return;
const heroSliderArrows = section.querySelectorAll(".slider_button");
const heroSliderItems = heroSliderWrap.querySelectorAll(".home_hero_slider_item");
const heroThumbnailContainer = section.querySelector(".custom_slider_thumbnail");
const totalHeroSlides = heroSliderItems.length;
let heroActiveIndex = 0;
let heroCurrentTimeline = null;
let heroAutoplayInterval;
function startHeroAutoplay() {
if (heroAutoplayInterval) heroAutoplayInterval.kill();
heroAutoplayInterval = gsap.delayedCall(7, () => {
goNextHero(heroActiveIndex + 1);
startHeroAutoplay();
});
}
function buildHeroThumbnails() {
while (heroThumbnailContainer.firstChild) {
heroThumbnailContainer.removeChild(heroThumbnailContainer.firstChild); // Safe clear without innerHTML
}
heroSliderItems.forEach((item, index) => {
const heroImgElement = item.querySelector(".home_hero_slider_image img");
if (!heroImgElement) return;
const heroImgSrc = heroImgElement.getAttribute("src");
const heroThumbnail = document.createElement("div");
heroThumbnail.className = `thumbnail ${index === 0 ? "active" : ""}`;
heroThumbnail.dataset.index = index;
const img = document.createElement("img");
img.className = "object-cover-fit";
img.src = heroImgSrc;
img.alt = `Thumbnail ${index + 1}`;
heroThumbnail.appendChild(img);
heroThumbnailContainer.appendChild(heroThumbnail);
});
// Animate thumbnails with GSAP
const heroThumbnails = heroThumbnailContainer.querySelectorAll('.thumbnail');
gsap.from(heroThumbnails, {
scale: 0,
duration: 0.5,
stagger: 0.1,
ease: "back.out(1.7)"
});
}
buildHeroThumbnails();
const heroThumbnails = section.querySelectorAll(".thumbnail");
function moveHeroSlide(nextIndex, forwards) {
if (nextIndex === heroActiveIndex) return;
if (heroCurrentTimeline) {
heroCurrentTimeline.kill();
heroCurrentTimeline = null;
}
// Animate thumbnails with GSAP
gsap.to(heroThumbnails, {
scale: 1,
duration: 0.3,
ease: "power1.out"
});
heroThumbnails.forEach(thumb => thumb.classList.remove("active"));
heroThumbnails[nextIndex].classList.add("active");
gsap.to(heroThumbnails[nextIndex], {
scale: 1.1,
duration: 0.3,
ease: "power1.out"
});
const prevHeroItem = heroSliderItems[heroActiveIndex];
const nextHeroItem = heroSliderItems[nextIndex];
const oldHeroIndex = heroActiveIndex;
heroActiveIndex = nextIndex;
// Set z-index with GSAP
heroSliderItems.forEach((item, i) => {
gsap.set(item, {
zIndex: i === nextIndex ? 2 : (i === oldHeroIndex ? 1 : 0)
});
});
const nextHeroSliderImage = nextHeroItem.querySelector(".home_hero_slider_image img");
if (nextHeroSliderImage) {
gsap.set(nextHeroSliderImage, { scale: 1.15 });
}
prevHeroItem.classList.add("is-active");
nextHeroItem.classList.add("is-active");
const heroTitleFrom = forwards ? 100 : -100;
const heroTitleDelay = forwards ? "<50%" : "<";
heroCurrentTimeline = gsap.timeline({
defaults: { duration: 1, ease: "power1.out" },
onComplete: () => {
prevHeroItem.classList.remove("is-active");
heroSliderItems.forEach((item, i) => {
gsap.set(item, { zIndex: i === heroActiveIndex ? 2 : 0 });
});
const activeHeroSliderImage = nextHeroItem.querySelector(".home_hero_slider_image img");
if (activeHeroSliderImage) {
gsap.to(activeHeroSliderImage, {
scale: 1,
duration: 4,
ease: "power1.out"
});
}
heroCurrentTimeline = null;
}
});
// Slide transitions with GSAP
if (forwards) {
heroCurrentTimeline.fromTo(
nextHeroItem,
{ clipPath: "polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)" },
{ clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, -30% 100%)" }
);
heroCurrentTimeline.fromTo(
prevHeroItem,
{ clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)" },
{ clipPath: "polygon(0% 0%, 0% 0%, -30% 100%, 0% 100%)" },
"<"
);
} else {
heroCurrentTimeline.fromTo(
nextHeroItem,
{ clipPath: "polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%)" },
{ clipPath: "polygon(0% 0%, 100% 0%, 130% 100%, 0% 100%)" }
);
heroCurrentTimeline.fromTo(
prevHeroItem,
{ clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)" },
{ clipPath: "polygon(100% 0%, 100% 0%, 100% 100%, 130% 100%)" },
"<"
);
}
// Animate text with GSAP
const heroTitleElements = nextHeroItem.querySelectorAll(".home_hero_slider_meta");
heroCurrentTimeline.fromTo(
heroTitleElements,
{ yPercent: heroTitleFrom, autoAlpha: 0 },
{ yPercent: 0, autoAlpha: 1, duration: 0.5 },
heroTitleDelay
);
}
function goNextHero(num) {
const nextHeroIndex = num >= totalHeroSlides ? 0 : num;
moveHeroSlide(nextHeroIndex, true);
startHeroAutoplay();
}
function goPrevHero(num) {
const nextHeroIndex = num < 0 ? totalHeroSlides - 1 : num;
moveHeroSlide(nextHeroIndex, false);
startHeroAutoplay();
}
// Initialize first slide animation with GSAP
const firstHeroSliderImage = heroSliderItems[0]?.querySelector(".home_hero_slider_image img");
if (firstHeroSliderImage) {
gsap.fromTo(
firstHeroSliderImage,
{ scale: 1.15 },
{ scale: 1, duration: 4, ease: "power1.out" }
);
}
gsap.set(heroThumbnails[0], { scale: 1.1 });
startHeroAutoplay();
// Setup arrow controls with GSAP hover effects
section.querySelectorAll(".slider_button").forEach(arrow => {
if (arrow.classList.contains("is-next")) {
arrow.addEventListener("click", () => {
goNextHero(heroActiveIndex + 1);
startHeroAutoplay();
});
}
if (arrow.classList.contains("is-prev")) {
arrow.addEventListener("click", () => {
goPrevHero(heroActiveIndex - 1);
startHeroAutoplay();
});
}
});
// Setup thumbnail controls with GSAP hover effects
heroThumbnails.forEach((thumb) => {
thumb.addEventListener("click", () => {
const index = parseInt(thumb.dataset.index);
moveHeroSlide(index, index > heroActiveIndex);
startHeroAutoplay();
});
});
});
}
.section_home_hero: Hero section container.
.home_hero_slider_item: Individual slides; scale and fade.
.home_hero_slider_image img: Images zoom out slowly.
.home_hero_slider_meta: Text slides in on change.
.custom_slider_thumbnail: Thumbnails container; thumbnails scale in.
.slider_button.is-next, .slider_button.is-prev: Arrows for navigation.
A section with class section_home_hero containing a div home_hero_slider with multiple home_hero_slider_item divs (each with image and meta text), a custom_slider_thumbnail div, and arrow buttons.
Autoplay delay: Change gsap.delayedCall(7, ...) to gsap.delayedCall(10, ...) for 10-second wait.
gsap.delayedCall(10, () => { ... });
Slide duration: In timeline, change defaults: { duration: 1, ... } to defaults: { duration: 1.5, ... } for slower transitions.
heroCurrentTimeline = gsap.timeline({
defaults: { duration: 1.5, ease: "power1.out" },
...
});
Ease: Change "power1.out" to "bounce.out" for bouncy feel.
Comment/Remove entire code shown in the Code Block 2
This animates a testimony slider with images, text, arrows, and autoplay.
// ============================================
// TESTIMONY SLIDER WITH GSAP
// ============================================
function initTestimonySliderAnimations() {
const testimonyImageWrap = document.querySelector('.testimony_image-wrap');
const testimonyImages = testimonyImageWrap?.querySelectorAll('.testimony_image') || [];
const testimonyTextWrap = document.querySelector('.testimony_text-wrap');
const testimonyTexts = testimonyTextWrap?.querySelectorAll('.testimony_text') || [];
const testimonyNextArrow = document.querySelector('.testimony_arrow.is-next');
const testimonyPrevArrow = document.querySelector('.testimony_arrow.is-prev');
if (!testimonyImageWrap || !testimonyTextWrap || !testimonyNextArrow || !testimonyPrevArrow ||
testimonyImages.length === 0 || testimonyTexts.length === 0) {
return;
}
let testimonyActiveIndex = 0;
let testimonyCurrentTimeline = null;
let testimonyAutoplayInterval = null;
function startTestimonyAutoplay() {
if (testimonyAutoplayInterval) testimonyAutoplayInterval.kill();
testimonyAutoplayInterval = gsap.delayedCall(7, () => {
goNextTestimony();
startTestimonyAutoplay();
});
}
function moveTestimonySlide(nextIndex, direction) {
if (nextIndex === testimonyActiveIndex) return;
if (testimonyCurrentTimeline) {
testimonyCurrentTimeline.kill();
testimonyCurrentTimeline = null;
}
const prevTestimonyImage = testimonyImages[testimonyActiveIndex];
const nextTestimonyImage = testimonyImages[nextIndex];
const prevTestimonyText = testimonyTexts[testimonyActiveIndex];
const nextTestimonyText = testimonyTexts[nextIndex];
testimonyImages.forEach((img, i) => img.classList.toggle('is-active', i === nextIndex));
testimonyTexts.forEach((text, i) => text.classList.toggle('is-active', i === nextIndex));
gsap.set(prevTestimonyImage, { zIndex: 1, autoAlpha: 1 });
gsap.set(nextTestimonyImage, { zIndex: 2, autoAlpha: 1 });
gsap.set(prevTestimonyText, { zIndex: 1, autoAlpha: 1 });
gsap.set(nextTestimonyText, { zIndex: 2, autoAlpha: 1 });
const oldTestimonyIndex = testimonyActiveIndex;
testimonyActiveIndex = nextIndex;
testimonyCurrentTimeline = gsap.timeline({
defaults: {
duration: 1,
ease: 'power2.inOut'
},
onComplete: () => {
if (testimonyActiveIndex !== oldTestimonyIndex) {
gsap.set(prevTestimonyImage, { autoAlpha: 0, zIndex: 0 });
gsap.set(prevTestimonyText, { autoAlpha: 0, zIndex: 0 });
}
testimonyImages.forEach((img, i) => {
if (i !== testimonyActiveIndex) {
gsap.set(img, { zIndex: 0, autoAlpha: 0 });
}
});
testimonyTexts.forEach((text, i) => {
if (i !== testimonyActiveIndex) {
gsap.set(text, { zIndex: 0, autoAlpha: 0 });
}
});
gsap.set(testimonyImages[testimonyActiveIndex], { zIndex: 2, autoAlpha: 1 });
gsap.set(testimonyTexts[testimonyActiveIndex], { zIndex: 2, autoAlpha: 1 });
testimonyCurrentTimeline = null;
}
});
if (direction === 'next') {
testimonyCurrentTimeline.fromTo(
nextTestimonyImage,
{ clipPath: 'polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)' },
{ clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' }
);
testimonyCurrentTimeline.fromTo(
prevTestimonyImage,
{ clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' },
{ clipPath: 'polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%)' },
'<'
);
} else {
testimonyCurrentTimeline.fromTo(
nextTestimonyImage,
{ clipPath: 'polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%)' },
{ clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' }
);
testimonyCurrentTimeline.fromTo(
prevTestimonyImage,
{ clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' },
{ clipPath: 'polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%)' },
'<'
);
}
testimonyCurrentTimeline.fromTo(
prevTestimonyText,
{ y: 0, autoAlpha: 1 },
{ y: -50, autoAlpha: 0, duration: 0.5 },
'<'
);
testimonyCurrentTimeline.fromTo(
nextTestimonyText,
{ y: 50, autoAlpha: 0 },
{ y: 0, autoAlpha: 1, duration: 0.5 },
'<'
);
}
function goNextTestimony() {
const nextTestimonyIndex = (testimonyActiveIndex + 1) % testimonyImages.length;
moveTestimonySlide(nextTestimonyIndex, 'next');
startTestimonyAutoplay();
}
function goPrevTestimony() {
const prevTestimonyIndex = (testimonyActiveIndex - 1 + testimonyImages.length) % testimonyImages.length;
moveTestimonySlide(prevTestimonyIndex, 'prev');
startTestimonyAutoplay();
}
// Initialize slider state with GSAP
testimonyImages.forEach((img, index) => {
img.classList.toggle('is-active', index === testimonyActiveIndex);
gsap.set(img, {
autoAlpha: index === testimonyActiveIndex ? 1 : 0,
clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)',
zIndex: index === testimonyActiveIndex ? 2 : 0
});
});
testimonyTexts.forEach((text, index) => {
text.classList.toggle('is-active', index === testimonyActiveIndex);
gsap.set(text, {
autoAlpha: index === testimonyActiveIndex ? 1 : 0,
y: index === testimonyActiveIndex ? 0 : 50,
zIndex: index === testimonyActiveIndex ? 2 : 0
});
});
// Setup click handlers
testimonyNextArrow.addEventListener('click', goNextTestimony);
testimonyPrevArrow.addEventListener('click', goPrevTestimony);
// Start autoplay with GSAP
startTestimonyAutoplay();
}
.testimony_image-wrap: Images container; images clip in/out.
.testimony_image: Individual images; fade and slide.
.testimony_text-wrap: Texts container.
.testimony_text: Individual texts; slide up/down.
.testimony_arrow.is-next, .testimony_arrow.is-prev: Arrows.
Divs testimony_image-wrap with multiple testimony_image, testimony_text-wrap with matching testimony_text, and arrow elements.
Autoplay: 7 → 5.
testimonyAutoplayInterval = gsap.delayedCall(5, () => { ... });
Duration: duration: 1 → 0.8.
testimonyCurrentTimeline = gsap.timeline({
defaults: { duration: 0.8, ease: 'power2.inOut' },
...
});
Text: duration: 0.5 → 0.7.
duration: 0.7
Ease: "power2.inOut" → "power3.inOut".
Comment/Remove: // function initTestimonySlider() {..}
Or Comment/Remove: The code from page settings that are shown in Code Block 3
This animates expertise items on hover, switching images and texts.
// ============================================
// EXPERTISE SECTION WITH GSAP
// ============================================
function initExpertiseAnimations() {
const expertiseFiguresContainer = document.querySelector('.home_expertise_figures');
const expertiseInnerContainer = document.querySelector('.home_expertise_inner');
const expertiseTextWrap = document.querySelector('.home_expertise_text_wrap');
const expertiseTextItems = expertiseTextWrap?.querySelectorAll('.home_expertise_text') || [];
if (!expertiseFiguresContainer || !expertiseInnerContainer || !expertiseTextItems.length) return;
const expertiseImageElements = expertiseFiguresContainer.querySelectorAll('.home_expertise_image');
const expertiseItemElements = expertiseInnerContainer.querySelectorAll('.home_expertise_item');
// Initialize with GSAP
gsap.set(expertiseImageElements, {
position: 'absolute',
top: 0,
left: 0,
autoAlpha: 0,
zIndex: 0,
scale: 1
});
gsap.set(expertiseImageElements[0], {
autoAlpha: 1,
zIndex: 2,
scale: 1
});
expertiseItemElements[0].classList.add('is-active');
expertiseTextItems[0].classList.add('is-active');
gsap.set(expertiseTextItems[0], { y: 0, autoAlpha: 1 });
gsap.set(Array.from(expertiseTextItems).slice(1), { y: 40, autoAlpha: 0 });
// Setup hover animations with GSAP
expertiseItemElements.forEach((item, itemIndex) => {
item.addEventListener('mouseenter', () => {
expertiseItemElements.forEach(el => el.classList.remove('is-active'));
item.classList.add('is-active');
const expertiseImageTransitionTL = gsap.timeline();
expertiseImageTransitionTL.to(expertiseImageElements, {
autoAlpha: 0,
zIndex: 0,
scale: 1,
duration: 0.3,
ease: 'power2.inOut'
});
expertiseImageTransitionTL.fromTo(
expertiseImageElements[itemIndex],
{ autoAlpha: 0, scale: 1.3, zIndex: 2 },
{ autoAlpha: 1, scale: 1, zIndex: 2, duration: 0.5, ease: 'power2.out' },
'-=0.2'
);
expertiseTextItems.forEach((text, index) => {
if (index === itemIndex) {
text.classList.add('is-active');
gsap.to(text, { y: 0, autoAlpha: 1, duration: 0.5, ease: 'power2.out' });
} else {
text.classList.remove('is-active');
gsap.to(text, { y: 40, autoAlpha: 0, duration: 0.5, ease: 'power2.out' });
}
});
});
});
}
.home_expertise_figures: Images container; images scale/fade on hover.
.home_expertise_image: Individual images.
.home_expertise_inner: Items container.
.home_expertise_item: Hoverable items.
.home_expertise_text_wrap: Texts container.
.home_expertise_text: Texts slide in/out.
Div home_expertise_figures with multiple home_expertise_image, home_expertise_inner with home_expertise_item, home_expertise_text_wrap with matching home_expertise_text.
Fade duration: Change duration: 0.3 to duration: 0.6 for slower hide.
expertiseImageTransitionTL.to(expertiseImageElements, {
autoAlpha: 0,
zIndex: 0,
scale: 1,
duration: 0.6,
ease: 'power2.inOut'
});
Scale duration: Change duration: 0.5 to duration: 0.7 for slower reveal.
expertiseImageTransitionTL.fromTo(
expertiseImageElements[itemIndex],
{ autoAlpha: 0, scale: 1.3, zIndex: 2 },
{ autoAlpha: 1, scale: 1, zIndex: 2, duration: 0.7, ease: 'power2.out' },
'-=0.2'
);
Ease: 'power2.out' → 'bounce.out'.
Comment/Remove: The code from page settings that are shown in Code Block 4
This animates location items on hover, switching images.
// ============================================
// LOCATION SECTION WITH GSAP
// ============================================
function initLocationAnimations() {
const locationTextElements = document.querySelectorAll('.location_text');
const locationImageContainer = document.querySelector('.home_location_image_list');
const locationImageItems = document.querySelectorAll('.home_location_image_item');
if (!locationTextElements.length || !locationImageContainer || !locationImageItems.length) return;
// Initialize with GSAP
gsap.set(locationImageItems, {
position: 'absolute',
top: 0,
left: 0,
autoAlpha: 0,
scale: 0.6
});
locationTextElements.forEach(textElement => {
const locationTitleElement = textElement.querySelector('.location_title');
if (locationTitleElement) locationTitleElement.classList.remove('is-active');
});
// Setup hover animations with GSAP
locationTextElements.forEach((textElement, textIndex) => {
textElement.addEventListener('mouseenter', () => {
locationTextElements.forEach(el => {
const title = el.querySelector('.location_title');
if (title) title.classList.remove('is-active');
});
const hoveredLocationTitle = textElement.querySelector('.location_title');
if (hoveredLocationTitle) hoveredLocationTitle.classList.add('is-active');
const locationImageTL = gsap.timeline();
locationImageTL.to(locationImageItems, {
autoAlpha: 0,
scale: 0.6,
duration: 0.3,
ease: 'power2.inOut'
});
locationImageTL.fromTo(
locationImageItems[textIndex],
{ autoAlpha: 0, scale: 0.6 },
{ autoAlpha: 1, scale: 1, duration: 0.5, ease: 'power2.out' },
'-=0.15'
);
});
});
}
.location_text: Hoverable text elements.
.location_title: Titles activate on hover.
.home_location_image_list: Images container.
.home_location_image_item: Images scale/fade on hover.
Multiple location_text divs each with location_title, home_location_image_list with matching home_location_image_item.
Hide duration: Change duration: 0.3 to duration: 0.5 for slower fade out.
locationImageTL.to(locationImageItems, {
autoAlpha: 0,
scale: 0.6,
duration: 0.5,
ease: 'power2.inOut'
});
Reveal duration: Change duration: 0.5 to duration: 0.8 for slower scale in.
locationImageTL.fromTo(
locationImageItems[textIndex],
{ autoAlpha: 0, scale: 0.6 },
{ autoAlpha: 1, scale: 1, duration: 0.8, ease: 'power2.out' },
'-=0.15'
);
Comment/Remove: The code from page settings that are shown in Code Block 5
This runs all functions when page loads.
// ============================================
// INITIALIZE ALL ANIMATIONS ON PAGE LOAD
// ============================================
document.addEventListener('DOMContentLoaded', function () {
initHeroSliderAnimations();
initTestimonySliderAnimations();
initExpertiseAnimations();
initLocationAnimations();
});
No new elements; calls other functions.
Comment out function calls: // initHeroSliderAnimations(); etc.
This animates counters to count up from 0 on scroll.
Elements like <div class="counter">1234</div> with target number as text. Note: Don't includ the comma in original content in Webflow.
// ============================================
// COUNTER ANIMATION WITH GSAP
// ============================================
function initCounterAnimations() {
const counterElements = document.querySelectorAll(".counter");
counterElements?.forEach(counterItem => {
gsap.from(counterItem, {
textContent: 0,
duration: 4,
ease: "power1.in",
snap: { textContent: 1 },
scrollTrigger: {
trigger: counterItem,
start: "top 95%",
once: true
},
onUpdate: function () {
const counterValue = Math.ceil(parseInt(counterItem.textContent) || 0);
counterItem.textContent = counterValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
});
});
}
// ============================================
// INITIALIZE ANIMATIONS ON PAGE LOAD
// ============================================
document.addEventListener('DOMContentLoaded', function () {
initCounterAnimations();
});
.counter: Text animates from 0 to value, with commas.
Duration: 4 → 6 for slower.
duration: 6
Reveal: 0.5 → 0.7.
duration: 0.7
Ease: "power1.in" → "power2.inOut".
ease: "power2.inOut"
Start: "top 95%" → "top 80%" for delayed trigger.
start: "top 80%"
Comment/Remove: The code from site settings that are shown in Code Block 7