working animations

This commit is contained in:
2026-03-13 22:18:44 +01:00
parent d71bb5d26e
commit 4293eef824
8 changed files with 44 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
import { useGSAP } from "@gsap/react"; import { useGSAP } from "@gsap/react";
import { useLayoutEffect, import { useEffect,
useLayoutEffect,
useRef, type ReactNode } from "react"; useRef, type ReactNode } from "react";
import { useGsapContext } from "~/app/_providers/GsapProvicer"; import { useGsapContext } from "~/app/_providers/GsapProvicer";
import { SplitText } from "gsap/SplitText"; import { SplitText } from "gsap/SplitText";
@@ -7,7 +8,7 @@ import gsap from 'gsap'
const AnimateTextIn = ({children,animation="type",position}:{children:ReactNode,animation?:"type"|"slide",position:gsap.Position}) => { const AnimateTextIn = ({children,animation="type",position}:{children:ReactNode,animation?:"type"|"slide",position:gsap.Position}) => {
const el = useRef<HTMLDivElement>(null) const el = useRef<HTMLDivElement>(null)
const gsapContext = useGsapContext(); const gsapContext = useGsapContext();
useGSAP(() => { useEffect(() => {
console.log("aniamte text with:",position) console.log("aniamte text with:",position)
const tl = gsap.timeline(); const tl = gsap.timeline();
const chars = new SplitText(el.current,{type:'chars'}) const chars = new SplitText(el.current,{type:'chars'})

View File

@@ -102,7 +102,10 @@ export default function AnimatedBackgroundContainer({
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const isDark = resolvedTheme === "dark"; let isDark = resolvedTheme === "dark";
if (resolvedTheme == undefined) {
isDark = true;
}
const palette = isDark ? PALETTES.dark : PALETTES.light; const palette = isDark ? PALETTES.dark : PALETTES.light;
/* Spawn particles */ /* Spawn particles */
@@ -264,7 +267,6 @@ export default function AnimatedBackgroundContainer({
minHeight: "100vh", minHeight: "100vh",
width: "100%", width: "100%",
overflow: "hidden", overflow: "hidden",
backgroundColor: palette.base,
transition: "background-color 0.6s ease", transition: "background-color 0.6s ease",
}} }}
> >

View File

@@ -7,19 +7,16 @@ const AnimatedPageTitle = (
) => { ) => {
const el = useRef<HTMLHeadingElement>(null) const el = useRef<HTMLHeadingElement>(null)
const gsapContext = useGsapContext(); const gsapContext = useGsapContext();
useGSAP(() => { useEffect(() => {
console.log("add animated title with:",position) console.log("add animated title with:",position)
const tl = gsap.timeline();
tl.addLabel("title")
const split = new SplitText(el.current, { type: "chars" }) const split = new SplitText(el.current, { type: "chars" })
tl.from(el.current, { opacity: 0 }) gsapContext?.addAnimation(gsap.to(el.current, { opacity: 100 }),position)
tl.from(split.chars, { gsapContext?.addAnimation(gsap.from(split.chars, {
stagger: 0.05, rotate: -90, opacity: 0, x: -10 stagger: 0.05, rotate: -90, opacity: 0, x: -10
}, '>') }),'>')
gsapContext?.addAnimation(tl,position)
}) })
return ( return (
<h1 className="text-4xl opacity-100 font-bold text-balance w-full" ref={el}> {text} </h1> <h1 className="text-4xl opacity-0 font-bold text-balance w-full" ref={el}> {text} </h1>
) )
} }

View File

@@ -4,10 +4,18 @@ import { Moon, Sun } from "lucide-react"
import { useEffect } from "react" import { useEffect } from "react"
type Props = {activeTheme:string|undefined} type Props = {activeTheme:string|undefined}
const ThemeIcon = (props:Props) => { const ThemeIcon = (props:Props) => {
if (props.activeTheme == "dark") { return (
return (<Sun/>) <>
} else { {props.activeTheme && props.activeTheme == 'dark' &&
return (<Moon/>) <Sun/>
} }
{props.activeTheme && props.activeTheme == 'light' &&
<Moon/>
}
{!props.activeTheme &&
<Sun/>
}
</>
)
} }
export default ThemeIcon; export default ThemeIcon;

View File

@@ -8,6 +8,9 @@ import ThemeIcon from "./ThemeIcon"
export function ThemeSwitch() { export function ThemeSwitch() {
const { setTheme, theme } = useTheme() const { setTheme, theme } = useTheme()
if (!theme) {
setTheme('dark')
}
const toggleTheme = () => { const toggleTheme = () => {
setTheme(theme == "dark" ? "light" : "dark") setTheme(theme == "dark" ? "light" : "dark")
} }

View File

@@ -27,12 +27,11 @@ export default function GsapProvider({ children }: { children: ReactNode }) {
if (!tl.current) { if (!tl.current) {
tl.current = gsap.timeline({ paused: true }) tl.current = gsap.timeline({ paused: true })
} }
console.log("resuming timeline:",tl.current)
return () => { console.log("gsap cleanup") } return () => { console.log("gsap cleanup") }
}) })
const addAnimation = useCallback((animation: gsap.core.TimelineChild, position: gsap.Position) => { const addAnimation = useCallback((animation: gsap.core.TimelineChild, position: gsap.Position) => {
console.log("add animation to:", position) console.log("add animation to:", position, tl.current !== undefined)
tl.current?.add(animation, position); tl.current?.add(animation, position);
},[]) },[])
const resetTimeline = useCallback(() => { const resetTimeline = useCallback(() => {
@@ -41,6 +40,7 @@ export default function GsapProvider({ children }: { children: ReactNode }) {
tl.current = gsap.timeline({paused:true}) tl.current = gsap.timeline({paused:true})
},[]) },[])
const resumeTimeline = useCallback(() => { const resumeTimeline = useCallback(() => {
console.log("resuming timeline:",tl.current)
tl.current?.resume() tl.current?.resume()
},[]) },[])
return ( return (

View File

@@ -1,23 +1,10 @@
'use client' 'use client'
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes" import { ThemeProvider as NextThemesProvider } from "next-themes"
export default function ThemeProvider({children}:{children: React.ReactNode}) { export default function ThemeProvider({children}:{children: React.ReactNode}) {
const [mounted,setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
})
if (mounted) {
return ( return (
<NextThemesProvider disableTransitionOnChange nonce="test" attribute="class" defaultTheme="dark"> <NextThemesProvider disableTransitionOnChange attribute="class" defaultTheme="dark">
{children} {children}
</NextThemesProvider> </NextThemesProvider>
) )
} else {
return (
<>
{children}
</>
)
}
} }

View File

@@ -1,6 +1,8 @@
'use client' 'use client'
import { useGSAP } from "@gsap/react"; import { useGSAP } from "@gsap/react";
import { useLayoutEffect, import { useEffect,
useEffectEvent,
useLayoutEffect,
useRef } from "react"; useRef } from "react";
import { trpc } from "~/app/_trpc/Client"; import { trpc } from "~/app/_trpc/Client";
import * as Card from "~/components/ui/card"; import * as Card from "~/components/ui/card";
@@ -8,21 +10,20 @@ import { useGsapContext } from "../_providers/GsapProvicer";
import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle"; import AnimatedPageTitle from "../_components/Animated/AnimatedPageTitle";
import { Spinner } from "~/components/ui/spinner"; import { Spinner } from "~/components/ui/spinner";
import AnimateTextIn from "../_components/Animated/AnimateIn"; import AnimateTextIn from "../_components/Animated/AnimateIn";
import gsap from 'gsap'
export default function MusicPage() { export default function MusicPage() {
const { data: tracks, isLoading } = trpc.music.list.useQuery(); const { data: tracks, isLoading } = trpc.music.list.useQuery();
const container = useRef<HTMLDivElement>(null)
const gsapContext = useGsapContext(); const gsapContext = useGsapContext();
useGSAP(() => { useEffect(() => {
if (tracks) {
gsapContext?.resumeTimeline() gsapContext?.resumeTimeline()
}
return () => { return () => {
console.log("page cleanup") console.log("page cleanup")
gsapContext?.resetTimeline() gsapContext?.resetTimeline()
} }
}); },[tracks]);
return (<> return (<>
<div ref={container} className="w-full h-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4"> <div className="w-full h-full max-w-4xl mx-auto px-4 py-8 flex flex-col gap-4">
<AnimatedPageTitle position={0} text="Just Some Music I Made"/> <AnimatedPageTitle position={0} text="Just Some Music I Made"/>
<AnimateTextIn position={0.5}> <AnimateTextIn position={0.5}>
<div className="flex flex-row h-8 content-center items-center"> <div className="flex flex-row h-8 content-center items-center">