MVp
This commit is contained in:
202
components/DogstudioAnimations.tsx
Normal file
202
components/DogstudioAnimations.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
'use client'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { animateSplit, animateWords } from '@/lib/animateSplit'
|
||||
|
||||
// Dynamic imports to avoid SSR issues
|
||||
let gsap: any
|
||||
let ScrollTrigger: any
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
gsap = require('gsap').gsap
|
||||
ScrollTrigger = require('gsap/ScrollTrigger').ScrollTrigger
|
||||
gsap.registerPlugin(ScrollTrigger)
|
||||
}
|
||||
|
||||
export default function DogstudioAnimations() {
|
||||
const initialized = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialized.current) return
|
||||
initialized.current = true
|
||||
|
||||
// Check if GSAP is available
|
||||
if (!gsap || !ScrollTrigger) return
|
||||
|
||||
// Check for reduced motion
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
if (prefersReducedMotion) return
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
// Animate main headline with character split
|
||||
const mainHeadline = document.querySelector('.h1')
|
||||
if (mainHeadline) {
|
||||
gsap.set(mainHeadline, { opacity: 1 })
|
||||
animateSplit(mainHeadline as HTMLElement, {
|
||||
delay: 0.5,
|
||||
stagger: 0.02,
|
||||
duration: 0.8
|
||||
})
|
||||
}
|
||||
|
||||
// Animate section headings as they come into view
|
||||
const sectionHeadings = gsap.utils.toArray<HTMLElement>('.h2')
|
||||
sectionHeadings.forEach((heading) => {
|
||||
gsap.set(heading, { opacity: 0 })
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: heading,
|
||||
start: 'top 80%',
|
||||
onEnter: () => {
|
||||
gsap.set(heading, { opacity: 1 })
|
||||
animateWords(heading, { stagger: 0.05 })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Animate cards with stagger
|
||||
const cards = gsap.utils.toArray<HTMLElement>('.card, .gallery-item, .testimonial')
|
||||
cards.forEach((card, i) => {
|
||||
gsap.from(card, {
|
||||
y: 60,
|
||||
opacity: 0,
|
||||
duration: 0.8,
|
||||
ease: 'power2.out',
|
||||
scrollTrigger: {
|
||||
trigger: card,
|
||||
start: 'top 85%',
|
||||
toggleActions: 'play none none reverse'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Animate chips with stagger
|
||||
const chips = gsap.utils.toArray<HTMLElement>('.chip')
|
||||
if (chips.length > 0) {
|
||||
gsap.from(chips, {
|
||||
scale: 0,
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
stagger: 0.1,
|
||||
ease: 'back.out(1.7)',
|
||||
scrollTrigger: {
|
||||
trigger: chips[0],
|
||||
start: 'top 80%',
|
||||
toggleActions: 'play none none reverse'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Animate gallery items with hover effects
|
||||
const galleryItems = gsap.utils.toArray<HTMLElement>('.gallery-item')
|
||||
galleryItems.forEach((item) => {
|
||||
const img = item.querySelector('img')
|
||||
if (!img) return
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
gsap.to(img, {
|
||||
scale: 1.05,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
gsap.to(img, {
|
||||
scale: 1,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
item.addEventListener('mouseenter', handleMouseEnter)
|
||||
item.addEventListener('mouseleave', handleMouseLeave)
|
||||
})
|
||||
|
||||
// Animate buttons with magnetic effect
|
||||
const buttons = gsap.utils.toArray<HTMLElement>('.btn')
|
||||
buttons.forEach((button) => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const rect = button.getBoundingClientRect()
|
||||
const x = e.clientX - rect.left - rect.width / 2
|
||||
const y = e.clientY - rect.top - rect.height / 2
|
||||
|
||||
gsap.to(button, {
|
||||
x: x * 0.1,
|
||||
y: y * 0.1,
|
||||
duration: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
gsap.to(button, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
duration: 0.5,
|
||||
ease: 'elastic.out(1, 0.3)'
|
||||
})
|
||||
}
|
||||
|
||||
button.addEventListener('mousemove', handleMouseMove)
|
||||
button.addEventListener('mouseleave', handleMouseLeave)
|
||||
})
|
||||
|
||||
// Parallax effect on hero image
|
||||
const heroImage = document.querySelector('.hero__media')
|
||||
if (heroImage) {
|
||||
gsap.to(heroImage, {
|
||||
yPercent: -20,
|
||||
ease: 'none',
|
||||
scrollTrigger: {
|
||||
trigger: heroImage,
|
||||
start: 'top bottom',
|
||||
end: 'bottom top',
|
||||
scrub: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Smooth reveal for lead text
|
||||
const leadText = document.querySelector('.lead')
|
||||
if (leadText) {
|
||||
gsap.from(leadText, {
|
||||
y: 30,
|
||||
opacity: 0,
|
||||
duration: 0.8,
|
||||
delay: 1.2,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
// Animate eyebrow
|
||||
const eyebrow = document.querySelector('.eyebrow')
|
||||
if (eyebrow) {
|
||||
gsap.from(eyebrow, {
|
||||
y: 20,
|
||||
opacity: 0,
|
||||
duration: 0.6,
|
||||
delay: 0.3,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
// Animate hero buttons
|
||||
const heroButtons = document.querySelectorAll('.hero .btn')
|
||||
if (heroButtons.length > 0) {
|
||||
gsap.from(heroButtons, {
|
||||
y: 20,
|
||||
opacity: 0,
|
||||
duration: 0.6,
|
||||
stagger: 0.1,
|
||||
delay: 1.5,
|
||||
ease: 'power2.out'
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return () => ctx.revert()
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user