fix cv animations

This commit is contained in:
2026-06-18 01:41:25 +02:00
parent 3e5be46503
commit 0d79adb104
15 changed files with 606 additions and 724 deletions

View File

@@ -1,3 +1,4 @@
"use client"
import { useRef, type HTMLAttributes, type ReactNode } from "react";
import { SplitText } from "gsap/SplitText";
import gsap from 'gsap'
@@ -11,6 +12,7 @@ const AnimateTextIn = ({
speed = 1,
scrollOnly = false,
once = false,
debugId,
className
}: {
children: ReactNode,
@@ -18,6 +20,7 @@ const AnimateTextIn = ({
position?: gsap.Position,
scrollOnly?: boolean,
once?: boolean,
debugId?: string,
speed?: number,
className?: HTMLAttributes<HTMLDivElement>['className']
}) => {
@@ -26,7 +29,7 @@ const AnimateTextIn = ({
position,
scrollOnly,
once,
debugId: `text-${position}`,
debugId: debugId ?? `text-${position}`,
makeReveal: (node) => {
// The wrapper starts at opacity 0 (so there's no flash of unsplit text);
// reveal the wrapper and let the per-character tween do the animation.

View File

@@ -9,6 +9,7 @@ const AnimatePopUp = ({
ease='elastic',
scrollOnly=false,
once=false,
debugId,
}:{
children:ReactNode
position:gsap.Position,
@@ -17,9 +18,10 @@ const AnimatePopUp = ({
ease?:gsap.EaseString|gsap.EaseFunction,
scrollOnly?:boolean,
once?:boolean,
debugId?:string,
}) => {
return (
<AnimatedDiv children={children} position={position} scrollOnly={scrollOnly} once={once} className={cn(className,'h-0 translate-y-[50] overflow-hidden')} height='auto' y={0} overflow='' ease={ease} duration={duration} />
<AnimatedDiv children={children} position={position} scrollOnly={scrollOnly} once={once} debugId={debugId} className={cn(className,'h-0 translate-y-[50] overflow-hidden')} height='auto' y={0} overflow='' ease={ease} duration={duration} />
)
}

View File

@@ -1,3 +1,4 @@
"use client"
import gsap from "gsap";
import { type HTMLAttributes, type ReactNode, useRef } from "react";
import { useReveal } from "./useReveal";

View File

@@ -3,7 +3,7 @@
import { useGSAP } from "@gsap/react"
import { ScrollTrigger } from "gsap/all"
import type { RefObject } from "react"
import { GSAP_DEBUG, useGsapContext } from "~/app/_providers/GsapProvicer"
import { GSAP_DEBUG, nearestScroller, useGsapContext } from "~/app/_providers/GsapProvicer"
export type UseRevealOptions = {
position: gsap.Position
@@ -39,9 +39,12 @@ export function useReveal(
const ctx = useGsapContext()
useGSAP(() => {
const el = ref.current
if (!el || !ctx) return
if (!el || !ctx) {
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:skip]", { debugId, hasEl: !!el, hasCtx: !!ctx })
return
}
const scroller = ctx.getScroller()
const scroller = nearestScroller(el)
const scrollerEl = scroller instanceof Element ? scroller : undefined
const rect = el.getBoundingClientRect()
@@ -53,6 +56,28 @@ export function useReveal(
bottom = r.top + r.height
}
const isInView = rect.bottom > top && rect.top < bottom
if (GSAP_DEBUG) {
const scrollerRect = scrollerEl?.getBoundingClientRect()
console.log("[cv-debug][useReveal:register]", {
debugId,
position,
scrollOnly,
once,
isInView,
rect: { top: rect.top, bottom: rect.bottom, height: rect.height },
viewport: { top, bottom },
scroller:
scroller === window
? "window"
: {
slot: scrollerEl?.getAttribute("data-slot"),
className: scrollerEl?.className,
clientHeight: scrollerEl?.clientHeight,
scrollHeight: scrollerEl?.scrollHeight,
rect: scrollerRect ? { top: scrollerRect.top, bottom: scrollerRect.bottom, height: scrollerRect.height } : undefined,
},
})
}
const reveal = makeReveal(el)
// A reveal that animates height (pop-ups) shifts every trigger below it.
@@ -83,19 +108,26 @@ export function useReveal(
if (isInView && !scrollOnly) {
// The shared timeline only decides *when* the entrance starts; the reveal
// plays independently so the ScrollTrigger can take it over afterwards.
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:schedule]", { debugId, position })
ctx.schedule(() => reveal.play(), position)
// `once` elements keep their revealed state — no scroll trigger at all.
if (!once) ctx.onReady(addReplayTrigger)
if (!once) {
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:onReady]", { debugId })
ctx.onReady(addReplayTrigger)
}
} else if (isInView) {
// scrollOnly + already on screen: no enter crossing will fire, so reveal
// now. Keep a trigger for scroll-out unless this is a `once` element.
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:play-now]", { debugId, position })
reveal.play()
if (!once) addReplayTrigger()
} else if (once) {
// Off-screen: reveal when first reached, then self-destruct so it never
// reverses.
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:scroll-once]", { debugId, position })
ScrollTrigger.create({ ...baseTrigger, once: true, onEnter: () => reveal.play() })
} else {
if (GSAP_DEBUG) console.log("[cv-debug][useReveal:scroll-trigger-only]", { debugId, position })
addReplayTrigger()
}
}, { dependencies: [] })