michaelpeskov/lib/animateSplit.ts

110 lines
2.1 KiB
TypeScript

// Dynamic import to avoid SSR issues
let gsap: any
if (typeof window !== 'undefined') {
gsap = require('gsap').gsap
}
export function splitTextIntoChars(element: HTMLElement): HTMLElement[] {
const text = element.textContent || ''
const chars: HTMLElement[] = []
element.innerHTML = ''
for (let i = 0; i < text.length; i++) {
const char = text[i]
const span = document.createElement('span')
span.textContent = char === ' ' ? '\u00A0' : char
span.setAttribute('data-char', i.toString())
span.style.display = 'inline-block'
span.style.willChange = 'transform'
element.appendChild(span)
chars.push(span)
}
return chars
}
export function animateSplit(element: HTMLElement, options: {
delay?: number
stagger?: number
duration?: number
ease?: string
} = {}) {
if (!gsap) return
const {
delay = 0,
stagger = 0.015,
duration = 0.8,
ease = 'power2.out'
} = options
const chars = splitTextIntoChars(element)
// Set initial state
gsap.set(chars, {
yPercent: 110,
rotateZ: 5,
opacity: 0,
transformOrigin: 'center bottom'
})
// Animate in
return gsap.to(chars, {
yPercent: 0,
rotateZ: 0,
opacity: 1,
stagger,
duration,
ease,
delay
})
}
export function animateWords(element: HTMLElement, options: {
delay?: number
stagger?: number
duration?: number
ease?: string
} = {}) {
if (!gsap) return
const {
delay = 0,
stagger = 0.08,
duration = 0.6,
ease = 'power2.out'
} = options
const text = element.textContent || ''
const words = text.split(' ')
element.innerHTML = ''
const wordElements = words.map(word => {
const span = document.createElement('span')
span.textContent = word + ' '
span.style.display = 'inline-block'
span.style.willChange = 'transform'
element.appendChild(span)
return span
})
// Set initial state
gsap.set(wordElements, {
yPercent: 100,
opacity: 0,
transformOrigin: 'center bottom'
})
// Animate in
return gsap.to(wordElements, {
yPercent: 0,
opacity: 1,
stagger,
duration,
ease,
delay
})
}