Skip to content

Commit

Permalink
Refactor for home animations and perf
Browse files Browse the repository at this point in the history
  • Loading branch information
krispya committed Jul 15, 2024
1 parent 84941f9 commit e9fe781
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 102 deletions.
183 changes: 183 additions & 0 deletions app/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
'use client'

import CausticOverlay from '@/components/scenes/caustic/CausticOverlay'
import CausticScene from '@/components/scenes/caustic/CausticScene'
import { useSpring, useTransition, a } from '@react-spring/web'
import { PerformanceMonitor } from '@react-three/drei'
import { Canvas, invalidate, useThree } from '@react-three/fiber'
import { usePathname } from 'next/navigation'
import { memo, useEffect, useRef, useState } from 'react'
import * as THREE from 'three'
import { create } from 'zustand'

THREE.Texture.DEFAULT_ANISOTROPY = 8

const Scene = memo(CausticScene)

export default function PmndrsCanvas() {
const parentRef = useRef<HTMLDivElement>(null!)
const [perfSucks, degrade] = useState(false)
const isPaused = useCanvasApi((state) => state.isPaused)
const onPause = useCanvasApi((state) => state.onPause)
const onPlay = useCanvasApi((state) => state.onPlay)
const isLoaded = useCanvasApi((state) => state.isLoaded)

const pathname = usePathname()
const isHome = useRef(pathname === '/')
isHome.current = pathname === '/'

const [props, springApi] = useSpring(() => ({
opacity: 0,
}))

useEffect(() => {
const onPlayHandler = () => {
springApi.start({
opacity: 1,
onChange: (result) => {
if (parentRef.current) parentRef.current.style.opacity = `${result.value.opacity}`
},
})
}

const onPauseHandler = () => {
springApi.start({
opacity: 0,
onChange: (result) => {
if (parentRef.current) parentRef.current.style.opacity = `${result.value.opacity}`
},
})
}

const unsubPlay = onPlay(onPlayHandler)
const unsubPause = onPause(onPauseHandler)

return () => {
unsubPlay()
unsubPause()
}
}, [onPause, onPlay, springApi])

useEffect(() => {
if (!isLoaded) return
if (!isHome.current) return

springApi.start({
opacity: 1,
onChange: (result) => {
if (parentRef.current) parentRef.current.style.opacity = `${result.value.opacity}`
},
})
}, [isLoaded, springApi])

// useTransition
const transitions = useTransition(isHome.current, {
from: { opacity: 0, y: 100 },
enter: (isHome) => ({ opacity: isHome ? 1 : 0, y: isHome ? 0 : 100 }),
leave: (isHome) => ({ opacity: isHome ? 0 : 1, y: isHome ? 100 : 0 }),
})

return (
<>
<CausticOverlay show={isHome.current} />
<Canvas
ref={(node) => {
if (!node) return
parentRef.current = node.parentElement!.parentElement! as HTMLDivElement
}}
shadows
dpr={[1, perfSucks ? 1.5 : 2]}
eventSource={document.getElementById('root')!}
eventPrefix="client"
camera={{ position: [20, 0.9, 20], fov: 26 }}
className="touch-action-none inset-0 opacity-0 will-change-[opacity]"
style={{
position: 'fixed',
width: '100vw',
pointerEvents: isPaused ? 'none' : 'auto',
}}
>
<SubscribeToFrameloop />
<Scene perfSucks={perfSucks} />
{/** PerfMon will detect performance issues */}
<PerformanceMonitor onDecline={() => degrade(true)} />
</Canvas>
</>
)
}

function SubscribeToFrameloop() {
const isPaused = useCanvasApi((state) => state.isPaused)
const state = useThree()
const onPlay = useCanvasApi((state) => state.onPlay)
const onPause = useCanvasApi((state) => state.onPause)

useEffect(() => {
const onPlayHandler = () => {
state.internal.active = true
invalidate()
}
const onPauseHandler = () => {
state.internal.active = false
}

const unsubPlay = onPlay(onPlayHandler)
const unsubPause = onPause(onPauseHandler)

return () => {
unsubPlay()
unsubPause()
}
}, [onPause, onPlay, state])

useEffect(() => {
state.internal.active = !isPaused
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return null
}

type CanvasApi = {
isPaused: boolean
isLoaded: boolean
pause: () => void
play: () => void
onPauseCallbacks: (() => void)[]
onPlayCallbacks: (() => void)[]
onPause: (callback: () => void) => () => void
onPlay: (callback: () => void) => () => void
setIsLoaded: (isLoaded: boolean) => void
}

export const useCanvasApi = create<CanvasApi>((set, get) => ({
isPaused: true,
isLoaded: false,
onPauseCallbacks: [],
onPlayCallbacks: [],
onPause: (callback) => {
set((state) => ({ onPauseCallbacks: [...state.onPauseCallbacks, callback] }))
return () =>
set((state) => ({
onPauseCallbacks: state.onPauseCallbacks.filter((fn) => fn !== callback),
}))
},
onPlay: (callback) => {
set((state) => ({ onPlayCallbacks: [...state.onPlayCallbacks, callback] }))
return () =>
set((state) => ({
onPlayCallbacks: state.onPlayCallbacks.filter((fn) => fn !== callback),
}))
},
pause: () => {
set({ isPaused: true })
const onPause = get().onPauseCallbacks
onPause.forEach((fn) => fn())
},
play: () => {
set({ isPaused: false })
const onPlay = get().onPlayCallbacks
onPlay.forEach((fn) => fn())
},
setIsLoaded: (isLoaded) => set({ isLoaded }),
}))
27 changes: 12 additions & 15 deletions app/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import dynamic from 'next/dynamic'
import CausticOverlay from '@/components/scenes/caustic/CausticOverlay'

// const CausticScene = dynamic(() => import('@/components/scenes/caustic/CausticScene'), {
// ssr: false,
// })
const MonitorsScene = dynamic(() => import('@/components/scenes/monitors/MonitorsScene'), {
ssr: false,
})
'use client'
import { useLayoutEffect } from 'react'
import { useCanvasApi } from './Canvas'

export function Hero() {
return (
<>
<MonitorsScene />
{/* <CausticOverlay /> */}
</>
)
const { pause, play } = useCanvasApi()

useLayoutEffect(() => {
play()
return () => pause()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return null
}
9 changes: 7 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import 'css/tailwind.css'
import 'pliny/search/algolia.css'
import 'remark-github-blockquote-alert/alert.css'
// import '../css/globals.css'

import Header from '@/components/PmndrsHeader'
import { NullFooter } from '@/components/NullFooter'
import Header from '@/components/PmndrsHeader'
import SectionContainer from '@/components/SectionContainer'
import siteMetadata from '@/data/siteMetadata'
import { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { Analytics, AnalyticsConfig } from 'pliny/analytics'
import { SearchConfig, SearchProvider } from 'pliny/search'
import { ThemeProviders } from './theme-providers'
import dynamic from 'next/dynamic'

const inter = Inter({
subsets: ['latin'],
Expand Down Expand Up @@ -59,6 +59,10 @@ export const metadata: Metadata = {
},
}

const PmndrsCanvas = dynamic(() => import('./Canvas'), {
ssr: false,
})

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html
Expand All @@ -79,6 +83,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<ThemeProviders>
<Analytics analyticsConfig={siteMetadata.analytics as AnalyticsConfig} />
<div className="relative z-[0]">
<PmndrsCanvas />
<SectionContainer>
<div className="flex h-screen flex-col justify-between font-sans">
<SearchProvider searchConfig={siteMetadata.search as SearchConfig}>
Expand Down
87 changes: 64 additions & 23 deletions components/scenes/caustic/CausticOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,73 @@
import { a, useTrail } from '@react-spring/web'

const charClasses =
'absolute text-[14vw] font-extrabold leading-[0.8em] text-[#3e3e3d] dark:text-white select-none'
'absolute text-[14vw] font-extrabold leading-[0.8em] dark:text-white select-none'

const chars = [
{ letter: 'P', styles: { top: 40, left: 40, color: 'rgb(62 61 61 / 80%)' } },
{ letter: 'M', styles: { top: 40, left: '20vw', color: 'rgb(62 61 61 / 70%)' } },
{ letter: 'N', styles: { top: 40, left: '40vw', color: 'rgb(62 61 61 / 60%)' } },
{ letter: 'D', styles: { top: '20vw', left: '20vw', color: 'rgb(62 61 61 / 65%)' } },
{ letter: 'R', styles: { bottom: 40, left: '40vw', color: 'rgb(62 61 61 / 55%)' } },
{ letter: 'S', styles: { bottom: 40, left: '60vw', color: 'rgb(62 61 61 / 50%)' } },
]

export default function CausticOverlay({ show = true }) {
const trails = useTrail(chars.length, {
x: show ? 0 : 20,
from: { x: 20 },
config: { mass: show ? 2 : 1, tension: 500, friction: 36 },
})

const trails2 = useTrail(chars.length, {
opacity: show ? 1 : 0,
from: { opacity: 0 },
config: { tension: 200, friction: 24, duration: show ? undefined : 100 },
})

export default function CausticOverlay() {
return (
<>
<div className="pointer-events-none absolute left-0 top-0 h-[100%] w-[100%] overflow-hidden">
<div className={charClasses} style={{ top: 40, left: 40 }}>
P
</div>
<div className={charClasses} style={{ top: 40, left: '20vw' }}>
M
</div>
<div className={charClasses} style={{ top: 40, left: '40vw' }}>
N
</div>
<div className={charClasses} style={{ top: '20vw', left: '20vw' }}>
D
</div>
<div className={charClasses} style={{ bottom: 40, left: '40vw' }}>
R
</div>
<div className={charClasses} style={{ bottom: 40, left: '60vw' }}>
S
</div>
<div className="pointer-events-none fixed left-0 top-0 z-[5] h-[100%] w-[100vw] overflow-hidden">
{trails.map((props, index) => (
<a.div
key={chars[index].letter}
style={{ ...props, opacity: trails2[index].opacity, ...chars[index].styles }}
className={charClasses}
>
{chars[index].letter}
</a.div>
))}
</div>

{/* {transitions(
(style, item) =>
item && (
<a.div
style={style}
className="pointer-events-none fixed left-0 top-0 z-[5] h-[100%] w-[100vw] overflow-hidden"
>
<div className={charClasses} style={{ top: 40, left: 40 }}>
P
</div>
<div className={charClasses} style={{ top: 40, left: '20vw' }}>
M
</div>
<div className={charClasses} style={{ top: 40, left: '40vw' }}>
N
</div>
<div className={charClasses} style={{ top: '20vw', left: '20vw' }}>
D
</div>
<div className={charClasses} style={{ bottom: 40, left: '40vw' }}>
R
</div>
<div className={charClasses} style={{ bottom: 40, left: '60vw' }}>
S
</div>
</a.div>
)
)} */}

{/* <div style={{ position: 'absolute', bottom: 120, left: 120, fontSize: '18px' }}>
Runtime caustics and soft shadows,
<br />
Expand All @@ -42,8 +85,6 @@ export default function CausticOverlay() {
</div>
<br />
</div> */}

<div className="fixed inset-0 -z-[1] bg-[#f6d9d9]" />
</>
)
}
Loading

0 comments on commit e9fe781

Please sign in to comment.