diff --git a/src/app/_components/Animated/AnimateIn.tsx b/src/app/_components/Animated/AnimateIn.tsx index a497914..c743630 100644 --- a/src/app/_components/Animated/AnimateIn.tsx +++ b/src/app/_components/Animated/AnimateIn.tsx @@ -1,12 +1,14 @@ import { useGSAP } from "@gsap/react"; -import { useRef, type ReactNode } from "react"; +import { useLayoutEffect, +useRef, type ReactNode } from "react"; import { useGsapContext } from "~/app/_providers/GsapProvicer"; import { SplitText } from "gsap/SplitText"; import gsap from 'gsap' -const AnimateTextIn = ({children,animation="type"}:{children:ReactNode,animation?:"type"|"slide",index?:gsap.Position}) => { +const AnimateTextIn = ({children,animation="type",position}:{children:ReactNode,animation?:"type"|"slide",position:gsap.Position}) => { const el = useRef(null) const gsapContext = useGsapContext(); useGSAP(() => { + console.log("aniamte text with:",position) const tl = gsap.timeline(); const chars = new SplitText(el.current,{type:'chars'}) tl.to(el.current,{opacity:100, duration:0}) @@ -18,8 +20,8 @@ const AnimateTextIn = ({children,animation="type"}:{children:ReactNode,animation tl.from(chars.chars,{opacity:0, duration: 0.01, stagger: {each: 0.04}, ease: 'bounce.inOut', scrollTrigger: el.current }) break } - gsapContext?.addAnimation(tl) - },{scope:el}) + gsapContext?.addAnimation(tl,position) + }) return (
{children} diff --git a/src/app/_components/Animated/AnimatedPageTitle.tsx b/src/app/_components/Animated/AnimatedPageTitle.tsx index c2a2f0f..a2e191a 100644 --- a/src/app/_components/Animated/AnimatedPageTitle.tsx +++ b/src/app/_components/Animated/AnimatedPageTitle.tsx @@ -1,24 +1,25 @@ -import { useGSAP } from "@gsap/react"; import { useRef } from "react"; +import { useGSAP } from "@gsap/react"; import { useEffect, useLayoutEffect, useRef } from "react"; import { useGsapContext } from "~/app/_providers/GsapProvicer"; import { SplitText } from "gsap/SplitText"; import gsap from 'gsap' const AnimatedPageTitle = ( - { text }: { text: string } + { text, position }: { text: string, position:gsap.Position } ) => { const el = useRef(null) const gsapContext = useGsapContext(); useGSAP(() => { + console.log("add animated title with:",position) const tl = gsap.timeline(); tl.addLabel("title") const split = new SplitText(el.current, { type: "chars" }) - tl.to(el.current, { opacity: 100 }) + tl.from(el.current, { opacity: 0 }) tl.from(split.chars, { stagger: 0.05, rotate: -90, opacity: 0, x: -10 }, '>') - gsapContext?.addAnimation(tl) - }, { scope: el }) + gsapContext?.addAnimation(tl,position) + }) return ( -

{text}

+

{text}

) } diff --git a/src/app/_providers/GsapProvicer.tsx b/src/app/_providers/GsapProvicer.tsx index c56918f..f9acb01 100644 --- a/src/app/_providers/GsapProvicer.tsx +++ b/src/app/_providers/GsapProvicer.tsx @@ -8,33 +8,43 @@ import { createContext, useCallback, useContext, useRef, useState, type ReactNod gsap.registerPlugin(useGSAP) gsap.registerPlugin(ScrollTrigger) gsap.registerPlugin(SplitText) -const GsapContext = createContext<{ addAnimation: (animation: gsap.core.TimelineChild) => void, resetTimeline: () => void} | null>(null) +const GsapContext = createContext<{ + addAnimation: ( + animation: gsap.core.TimelineChild, + position: gsap.Position + ) => void, + resetTimeline: () => void, + resumeTimeline: () => void +} | null>(null) export function useGsapContext() { return useContext(GsapContext) } export default function GsapProvider({ children }: { children: ReactNode }) { - const [tl, setTl] = useState(); - const indexRef = useRef(0) + const tl = useRef(null) const { contextSafe } = useGSAP(() => { - console.log("App effect (create timeline)"); - const tl = gsap.timeline(); - setTl(() => tl); - }, []); - - const addAnimation = useCallback((animation: gsap.core.TimelineChild) => { - indexRef.current += 1; - console.log(indexRef.current) - tl && tl.add(animation, indexRef.current * 2); - }, [tl]); + if (!tl.current) { + tl.current = gsap.timeline({ paused: true }) + } + console.log("resuming timeline:",tl.current) + return () => { console.log("gsap cleanup") } + }) + + const addAnimation = useCallback((animation: gsap.core.TimelineChild, position: gsap.Position) => { + console.log("add animation to:", position) + tl.current?.add(animation, position); + },[]) const resetTimeline = useCallback(() => { - const tl = gsap.timeline(); - setTl(() => tl) - indexRef.current = 0; - },[tl]) + tl.current?.kill() + tl.current?.revert() + tl.current = gsap.timeline({paused:true}) + },[]) + const resumeTimeline = useCallback(() => { + tl.current?.resume() + },[]) return ( - + {children} ) diff --git a/src/app/cv/page.tsx b/src/app/cv/page.tsx index 47cb146..f032cea 100644 --- a/src/app/cv/page.tsx +++ b/src/app/cv/page.tsx @@ -33,11 +33,10 @@ export default function CvPage() { } } useGSAP(() => { - gsapContext?.resetTimeline() const items = gsap?.utils.toArray('.gsapan'); let dir = Direction.Left; items?.forEach(item => { - gsapContext?.addAnimation(gsap.from(item, nextGsapConf(dir))) + gsapContext?.addAnimation(gsap.from(item, nextGsapConf(dir)),0) if (dir == Direction.Down) { dir = Direction.Left } else { diff --git a/src/app/music/page.tsx b/src/app/music/page.tsx index 62d36ba..cbf5a71 100644 --- a/src/app/music/page.tsx +++ b/src/app/music/page.tsx @@ -1,6 +1,7 @@ 'use client' import { useGSAP } from "@gsap/react"; -import { useRef } from "react"; +import { useLayoutEffect, +useRef } from "react"; import { trpc } from "~/app/_trpc/Client"; import * as Card from "~/components/ui/card"; import { useGsapContext } from "../_providers/GsapProvicer"; @@ -13,24 +14,17 @@ export default function MusicPage() { const container = useRef(null) const gsapContext = useGsapContext(); useGSAP(() => { - gsapContext?.resetTimeline() - const items = gsap.utils.toArray('.gsapan'); - const tl = gsap.timeline(); - items.map(item => { - const player = item.querySelector('.player'); - tl.from( - item, { x: -100, opacity: 0, duration: 0.5, ease: 'power2.inOut', scrollTrigger: item },'<' - ).from( - player, { y: 10, opacity: 0, duration: 0.5, ease: 'power2.inOut' } - , '<0.3') - gsapContext?.addAnimation(tl); - }) - }, { scope: container, dependencies: [isLoading] }); + gsapContext?.resumeTimeline() + return () => { + console.log("page cleanup") + gsapContext?.resetTimeline() + } + }); return (<>
- - + +

All works on this page are licensed under:

CC BY-NC-SA 4.0 @@ -40,10 +34,10 @@ export default function MusicPage() {
- {tracks && tracks.map((track) => ( - + {tracks && tracks.map((track,i) => ( + - + {track.title} @@ -55,7 +49,7 @@ export default function MusicPage() { Your browser does not support the audio element. - + ))} {!isLoading && !tracks?.length &&
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 3a27cb7..969f79e 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -1,5 +1,7 @@ -import * as React from "react" - +import { useGSAP } from "@gsap/react";import * as React from "react" +import { useRef } from "react"; +import { useGsapContext } from "~/app/_providers/GsapProvicer"; +import gsap from 'gsap' import { cn } from "~/lib/utils" function Card({ @@ -20,6 +22,35 @@ function Card({ ) } +function AnimatedCard({ + className, + position = 0, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm", position: gsap.Position }) { + const gsapContext = useGsapContext() + const ref = useRef(null) + useGSAP(() => { + gsapContext?.addAnimation(gsap.from(ref.current,{ + x: -100, + opacity: 0, + duration: 0.5 + }),position) + }) + return ( +
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl bg-opacity-60 backdrop-blur-sm", + className + )} + {...props} + /> + ) +} + function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (