update home,about,solutions
This commit is contained in:
@@ -19,6 +19,11 @@ export default function AnimationProvider({ children }: { children: React.ReactN
|
||||
duration: 0.8,
|
||||
});
|
||||
|
||||
// Mobile browsers fire `resize` when the address bar shows/hides while
|
||||
// scrolling. Refreshing ScrollTrigger on every one of those caused jumpy /
|
||||
// re-hidden animations on phones. Ignore those spurious resizes.
|
||||
ScrollTrigger.config({ ignoreMobileResize: true });
|
||||
|
||||
const initDecorativeBlocks = () => {
|
||||
// Clean up previous block triggers to avoid duplicates
|
||||
ScrollTrigger.getAll().forEach((t) => {
|
||||
@@ -27,6 +32,7 @@ export default function AnimationProvider({ children }: { children: React.ReactN
|
||||
}
|
||||
});
|
||||
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
const decorativeBlocks = document.querySelectorAll(".block-decoration");
|
||||
decorativeBlocks.forEach((block) => {
|
||||
ScrollTrigger.create({
|
||||
@@ -50,6 +56,14 @@ export default function AnimationProvider({ children }: { children: React.ReactN
|
||||
block.classList.remove("animated");
|
||||
},
|
||||
});
|
||||
|
||||
// ScrollTrigger does not fire onEnter for blocks already in view at
|
||||
// creation — on larger / taller screens those stayed un-animated.
|
||||
// Reveal any block already intersecting the viewport.
|
||||
const r = block.getBoundingClientRect();
|
||||
if (r.top < vh && r.bottom > 0) {
|
||||
block.classList.add("animated");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,15 +97,22 @@ export default function AnimationProvider({ children }: { children: React.ReactN
|
||||
};
|
||||
window.addEventListener("load", handleLoad);
|
||||
|
||||
// Also refresh on window resize
|
||||
// Refresh on window resize — debounced so dragging the window across
|
||||
// breakpoints recomputes trigger positions once it settles, instead of
|
||||
// thrashing on every intermediate pixel.
|
||||
let resizeTimer: ReturnType<typeof setTimeout>;
|
||||
const handleResize = () => {
|
||||
initDecorativeBlocks();
|
||||
ScrollTrigger.refresh(true);
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => {
|
||||
initDecorativeBlocks();
|
||||
ScrollTrigger.refresh(true);
|
||||
}, 200);
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
timeouts.forEach(clearTimeout);
|
||||
clearTimeout(resizeTimer);
|
||||
window.removeEventListener("load", handleLoad);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
ScrollTrigger.getAll().forEach((t) => t.kill());
|
||||
|
||||
@@ -9,6 +9,71 @@ if (typeof window !== "undefined") {
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Shared scroll-reveal trigger factory.
|
||||
|
||||
ScrollTrigger does NOT fire `onEnter` for elements that are ALREADY inside
|
||||
the viewport when the trigger is created. On taller / larger screens more
|
||||
content sits above the fold on load, so those elements stayed stuck at
|
||||
opacity:0 (invisible). This was the root cause of "animations work on my
|
||||
laptop but break on bigger screens / other devices".
|
||||
|
||||
This factory fixes it by revealing any element already intersecting the
|
||||
viewport right after creation, so behaviour is identical on every screen
|
||||
size. It also honours prefers-reduced-motion by revealing instantly.
|
||||
============================================================ */
|
||||
function createRevealTrigger(opts: {
|
||||
trigger: Element;
|
||||
start?: string;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
triggerOnce?: boolean;
|
||||
}): () => void {
|
||||
const { trigger, start = "top 88%", show, hide, triggerOnce = false } = opts;
|
||||
|
||||
// Reduced motion: reveal immediately, no scroll dependency.
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
show();
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const st = ScrollTrigger.create({
|
||||
trigger,
|
||||
start,
|
||||
onEnter: (self) => {
|
||||
show();
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) show();
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) hide();
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) hide();
|
||||
},
|
||||
});
|
||||
|
||||
// Reveal-if-already-in-view (deferred one frame so layout is measured).
|
||||
const raf = requestAnimationFrame(() => {
|
||||
const r = trigger.getBoundingClientRect();
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
if (r.top < vh && r.bottom > 0) {
|
||||
show();
|
||||
if (triggerOnce) st.kill();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
st.kill();
|
||||
};
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
1. ScrollReveal
|
||||
Fade-in + slide-up on scroll. The workhorse animation.
|
||||
@@ -40,47 +105,19 @@ export function ScrollReveal({
|
||||
|
||||
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 88%",
|
||||
onEnter: (self) => {
|
||||
gsap.to(el, {
|
||||
y: 0,
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.to(el, {
|
||||
y: 0,
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () =>
|
||||
gsap.to(el, {
|
||||
y: 0,
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
const hide = () => gsap.set(el, { y: yOffset, x: xOffset, opacity: 0 });
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
|
||||
}, [delay, duration, yOffset, xOffset, triggerOnce]);
|
||||
|
||||
return (
|
||||
@@ -122,47 +159,19 @@ export function RevealText({
|
||||
|
||||
gsap.set(items, { y: "110%", opacity: 0 });
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 88%",
|
||||
onEnter: (self) => {
|
||||
gsap.to(items, {
|
||||
y: "0%",
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power4.out",
|
||||
stagger: type === "chars" ? 0.02 : 0.04,
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.to(items, {
|
||||
y: "0%",
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power4.out",
|
||||
stagger: type === "chars" ? 0.02 : 0.04,
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(items, { y: "110%", opacity: 0 });
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(items, { y: "110%", opacity: 0 });
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () =>
|
||||
gsap.to(items, {
|
||||
y: "0%",
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power4.out",
|
||||
stagger: type === "chars" ? 0.02 : 0.04,
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
const hide = () => gsap.set(items, { y: "110%", opacity: 0 });
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
|
||||
}, [children, type, delay, duration, triggerOnce]);
|
||||
|
||||
const renderContent = () => {
|
||||
@@ -290,45 +299,18 @@ export function StaggerChildren({
|
||||
|
||||
gsap.set(items, { y: yOffset, opacity: 0 });
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 85%",
|
||||
onEnter: (self) => {
|
||||
gsap.to(items, {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
stagger,
|
||||
overwrite: "auto",
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.to(items, {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
stagger,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(items, { y: yOffset, opacity: 0 });
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(items, { y: yOffset, opacity: 0 });
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () =>
|
||||
gsap.to(items, {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
stagger,
|
||||
overwrite: "auto",
|
||||
});
|
||||
const hide = () => gsap.set(items, { y: yOffset, opacity: 0 });
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, start: "top 85%", show, hide, triggerOnce });
|
||||
}, [stagger, duration, yOffset, triggerOnce]);
|
||||
|
||||
return (
|
||||
@@ -365,45 +347,18 @@ export function ScaleReveal({
|
||||
|
||||
gsap.set(el, { scale: 0.85, opacity: 0 });
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 88%",
|
||||
onEnter: (self) => {
|
||||
gsap.to(el, {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.to(el, {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { scale: 0.85, opacity: 0 });
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { scale: 0.85, opacity: 0 });
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () =>
|
||||
gsap.to(el, {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
const hide = () => gsap.set(el, { scale: 0.85, opacity: 0 });
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
|
||||
}, [delay, duration, triggerOnce]);
|
||||
|
||||
return (
|
||||
@@ -443,45 +398,18 @@ export function SlideReveal({
|
||||
const xStart = direction === "left" ? -60 : 60;
|
||||
gsap.set(el, { x: xStart, opacity: 0 });
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 88%",
|
||||
onEnter: (self) => {
|
||||
gsap.to(el, {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.to(el, {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { x: xStart, opacity: 0 });
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
gsap.set(el, { x: xStart, opacity: 0 });
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () =>
|
||||
gsap.to(el, {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
duration,
|
||||
ease: "power3.out",
|
||||
delay,
|
||||
overwrite: "auto",
|
||||
});
|
||||
const hide = () => gsap.set(el, { x: xStart, opacity: 0 });
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, show, hide, triggerOnce });
|
||||
}, [direction, delay, duration, triggerOnce]);
|
||||
|
||||
return (
|
||||
@@ -573,43 +501,18 @@ export function CountUp({
|
||||
const el = elementRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const trigger = ScrollTrigger.create({
|
||||
trigger: el,
|
||||
start: "top 90%",
|
||||
onEnter: (self) => {
|
||||
const obj = { val: start };
|
||||
gsap.to(obj, {
|
||||
val: end,
|
||||
duration,
|
||||
ease: "power2.out",
|
||||
onUpdate: () => setValue(obj.val),
|
||||
});
|
||||
if (triggerOnce) self.kill();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
if (!triggerOnce) {
|
||||
const obj = { val: start };
|
||||
gsap.to(obj, {
|
||||
val: end,
|
||||
duration,
|
||||
ease: "power2.out",
|
||||
onUpdate: () => setValue(obj.val),
|
||||
});
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
if (!triggerOnce) {
|
||||
setValue(start);
|
||||
}
|
||||
},
|
||||
onLeaveBack: () => {
|
||||
if (!triggerOnce) {
|
||||
setValue(start);
|
||||
}
|
||||
},
|
||||
});
|
||||
const show = () => {
|
||||
const obj = { val: start };
|
||||
gsap.to(obj, {
|
||||
val: end,
|
||||
duration,
|
||||
ease: "power2.out",
|
||||
onUpdate: () => setValue(obj.val),
|
||||
});
|
||||
};
|
||||
const hide = () => setValue(start);
|
||||
|
||||
return () => trigger?.kill();
|
||||
return createRevealTrigger({ trigger: el, start: "top 90%", show, hide, triggerOnce });
|
||||
}, [start, end, duration, triggerOnce]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user