Instruction
Easily customize the GSAP animations used throughout the template. To edit the GSAP custom code, navigate to the relevant Page Settings and update the code placed in the “Before </body> tag” section.
<!-- Lenis CSS -->
<link rel="stylesheet" href="https://unpkg.com/lenis@1.1.20/dist/lenis.css">
<!-- Lenis JS -->
<script src="https://unpkg.com/lenis@1.3.1/dist/lenis.min.js"></script>
<script>
window.addEventListener("DOMContentLoaded", () => {
/*
--------------------------------
COUNTER ANIMATION
--------------------------------
*/
gsap.utils.toArray(".counter-number").forEach((counter) => {
const textEl = counter.querySelector("*") || counter;
const original = textEl.textContent.trim();
const match = original.match(/^([^0-9]*)([0-9.,]+)(.*)$/);
if (!match) return;
const prefix = match[1];
const numberPart = match[2].replace(/,/g, "");
const suffix = match[3];
const hasDecimal = numberPart.includes(".");
const target = parseFloat(numberPart);
if (Number.isNaN(target)) return;
const value = {
current: 0
};
gsap.to(value, {
current: target,
duration: 1.2,
ease: "power1.out",
scrollTrigger: {
trigger: counter,
start: "top 90%",
once: true
},
onUpdate: () => {
const currentValue = hasDecimal
? value.current.toFixed(1)
: Math.floor(value.current).toLocaleString();
textEl.textContent =
prefix +
currentValue +
suffix;
}
});
});
/*
--------------------------------
LENIS SMOOTH SCROLL
--------------------------------
*/
if (window.matchMedia("(min-width: 768px)").matches) {
const lenis = new Lenis({
duration: 1,
smoothWheel: true,
smoothTouch: false,
allowNestedScroll: true,
easing: (t) => {
return 1 - Math.pow(1 - t, 3);
}
});
const raf = (time) => {
lenis.raf(time);
requestAnimationFrame(raf);
};
requestAnimationFrame(raf);
}
/*
--------------------------------
IMAGE TRAIL
--------------------------------
*/
function initImageTrail(wrapper, trailImages, options) {
const wrapperPosition =
getComputedStyle(wrapper).position;
if (wrapperPosition === "static") {
wrapper.style.position = "relative";
}
trailImages.forEach((item) => {
item.style.position = "absolute";
item.style.pointerEvents = "none";
gsap.set(item, {
autoAlpha: 0
});
});
let globalIndex = 0;
let last = {
x: 0,
y: 0
};
let isActive = false;
function handleMove(e) {
const rect =
wrapper.getBoundingClientRect();
const x =
e.clientX - rect.left;
const y =
e.clientY - rect.top;
const distance =
Math.hypot(
x - last.x,
y - last.y
);
if (
distance >
window.innerWidth / options.moveDistance
) {
const item =
trailImages[
globalIndex % trailImages.length
];
globalIndex++;
const img =
item.querySelector("img");
gsap.killTweensOf(item);
if (img) {
gsap.killTweensOf(img);
}
const itemRect =
item.getBoundingClientRect();
const startX =
last.x - itemRect.width / 2;
const startY =
last.y - itemRect.height / 2;
const endX =
x - itemRect.width / 2;
const endY =
y - itemRect.height / 2;
gsap.set(item, {
left: startX,
top: startY,
zIndex: globalIndex
});
if (img) {
gsap.set(img, {
opacity: 1,
scale: 1
});
}
const tl =
gsap.timeline();
tl.fromTo(
item,
{
autoAlpha: 0
},
{
autoAlpha: 1,
duration: 0.2
}
);
tl.fromTo(
item,
{
left: startX,
top: startY
},
{
left: endX,
top: endY,
duration: 0.5
},
"<"
);
if (img) {
tl.to(
img,
{
opacity: 0,
scale: 0.6,
duration: 0.2
}
);
} else {
tl.to(
item,
{
autoAlpha: 0,
scale: 0.6,
duration: 0.2
}
);
}
last = {
x,
y
};
}
}
function start() {
if (isActive) return;
if (
window.innerWidth <
options.minWidth
) {
return;
}
isActive = true;
wrapper.addEventListener(
"mousemove",
handleMove
);
}
function stop() {
if (!isActive) return;
isActive = false;
wrapper.removeEventListener(
"mousemove",
handleMove
);
}
function handleResize() {
if (
window.innerWidth <
options.minWidth
) {
stop();
} else {
start();
}
}
window.addEventListener(
"resize",
handleResize
);
start();
}
/*
--------------------------------
IMAGE TRAIL INITIALIZATION
--------------------------------
*/
const wrapper =
document.querySelector(
'[data-trail="wrapper"]'
);
const trailImages =
gsap.utils.toArray(
'[data-trail="item"]'
);
if (
wrapper &&
trailImages.length
) {
initImageTrail(
wrapper,
trailImages,
{
minWidth: 992,
moveDistance: 15
}
);
}
});
</script>