Files
vesta/components/sections/CloudRevealSection.tsx
T
2026-07-30 12:15:17 +03:00

172 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useRef, useState } from 'react'
import { useTranslations } from 'next-intl'
const TOTAL_FRAMES = 298
const FRAME_PATH = (n: number) =>
`/frames/ezgif-frame-${String(n).padStart(3, '0')}.jpg`
export default function CloudRevealSection() {
const t = useTranslations('hero')
const sectionRef = useRef<HTMLDivElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const textRef = useRef<HTMLDivElement>(null)
const framesRef = useRef<HTMLImageElement[]>([])
const currentFrameRef = useRef(0)
const rafRef = useRef<number | null>(null)
const [loaded, setLoaded] = useState(false)
const [loadProgress, setLoadProgress] = useState(0)
// Frame'leri sıralı preload et
useEffect(() => {
const images: HTMLImageElement[] = new Array(TOTAL_FRAMES)
let loadedCount = 0
for (let i = 0; i < TOTAL_FRAMES; i++) {
const img = new Image()
img.src = FRAME_PATH(i + 1)
img.onload = () => {
loadedCount++
setLoadProgress(Math.round((loadedCount / TOTAL_FRAMES) * 100))
if (loadedCount === TOTAL_FRAMES) {
framesRef.current = images
setLoaded(true)
// İlk frame'i hemen çiz
drawFrame(0, images)
}
}
images[i] = img
}
}, [])
function drawFrame(index: number, images?: HTMLImageElement[]) {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const imgs = images || framesRef.current
const img = imgs[index]
if (!img || !img.complete || img.naturalWidth === 0) return
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
}
// Scroll handler
useEffect(() => {
if (!loaded) return
const section = sectionRef.current
const text = textRef.current
if (!section || !text) return
const handleScroll = () => {
const rect = section.getBoundingClientRect()
const sectionHeight = section.offsetHeight
const windowH = window.innerHeight
const scrolled = -rect.top
const total = sectionHeight - windowH
const progress = Math.min(Math.max(scrolled / total, 0), 1)
// Hangi frame'i göstereceğimizi hesapla
const frameIndex = Math.min(
Math.floor(progress * (TOTAL_FRAMES - 1)),
TOTAL_FRAMES - 1
)
// Sadece frame değiştiyse çiz (RAF ile)
if (frameIndex !== currentFrameRef.current) {
currentFrameRef.current = frameIndex
if (rafRef.current) cancelAnimationFrame(rafRef.current)
rafRef.current = requestAnimationFrame(() => drawFrame(frameIndex))
}
// Tagline: ilk %15'te görünür, sonra solar
if (progress < 0.15) {
const opacity = 1 - progress / 0.15
text.style.opacity = String(opacity)
text.style.transform = `translateY(${progress * 40}px)`
} else {
text.style.opacity = '0'
}
}
window.addEventListener('scroll', handleScroll, { passive: true })
return () => {
window.removeEventListener('scroll', handleScroll)
if (rafRef.current) cancelAnimationFrame(rafRef.current)
}
}, [loaded])
// Canvas boyutunu ekrana sığdır
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
// Resize sonrası mevcut frame'i yeniden çiz
drawFrame(currentFrameRef.current)
}
resize()
window.addEventListener('resize', resize)
return () => window.removeEventListener('resize', resize)
}, [loaded])
return (
<div ref={sectionRef} style={{ height: '500vh' }}>
<div className="sticky top-0 h-screen w-full overflow-hidden bg-black">
{/* Canvas — frame'ler buraya çizilir */}
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full"
style={{ objectFit: 'cover' }}
/>
{/* Loading ekranı */}
{!loaded && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black z-20">
<div className="w-48 h-px bg-white/10 mb-3">
<div
className="h-full bg-white/60 transition-all duration-150"
style={{ width: `${loadProgress}%` }}
/>
</div>
<p className="text-white/30 text-xs tracking-widest uppercase">
{loadProgress}%
</p>
</div>
)}
{/* Tagline */}
<div
ref={textRef}
className="absolute bottom-16 left-0 right-0 z-10 text-center px-6"
style={{ willChange: 'opacity, transform', transition: 'none' }}
>
<p className="text-white/60 text-xs tracking-[0.4em] uppercase mb-3">
Vesta Muğla
</p>
<h1 className="text-white text-4xl md:text-6xl font-serif font-light leading-tight drop-shadow-2xl">
{t('tagline')}
</h1>
<p className="text-white/50 mt-4 text-base md:text-lg font-light">
{t('subtitle')}
</p>
</div>
{/* Scroll indicator */}
{loaded && (
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-10 flex flex-col items-center gap-2">
<span className="text-white/30 text-xs tracking-widest uppercase">
{t('scroll')}
</span>
<div className="w-px h-10 bg-white/20 animate-pulse" />
</div>
)}
</div>
</div>
)
}