type SpriteProps = { src: string width: number height: number frames: number frameDuration: number columns?: number class?: string style?: string playing?: boolean } export const Sprite = (props: SpriteProps) => { const { src, width, height, frames, frameDuration, columns, class: className, style, playing = true, } = props const isGrid = columns !== undefined const rows = isGrid ? Math.ceil(frames / columns!) : 1 const cols = isGrid ? columns! : frames const sheetWidth = cols * width const sheetHeight = rows * height const totalDuration = frames * frameDuration const keyframeId = `sprite-${Bun.hash( `${src}-${width}-${height}-${frames}-${frameDuration}-${columns}` ).toString(36)}` const keyframes = isGrid ? generateGridKeyframes(keyframeId, width, height, frames, columns!) : generateStripKeyframes(keyframeId, sheetWidth) const divStyle = [ `width:${width}px`, `height:${height}px`, `background-image:url('${src}')`, `background-size:${sheetWidth}px ${sheetHeight}px`, `animation:${keyframeId} ${totalDuration}ms steps(${frames}) infinite`, playing ? "" : "animation-play-state:paused", style, ] .filter(Boolean) .join(";") return ( <>
> ) } const generateStripKeyframes = (id: string, sheetWidth: number): string => `@keyframes ${id}{from{background-position:0 0}to{background-position:-${sheetWidth}px 0}}` const generateGridKeyframes = ( id: string, width: number, height: number, frames: number, columns: number ): string => { const steps: string[] = [] for (let i = 0; i < frames; i++) { const col = i % columns const row = Math.floor(i / columns) const x = -col * width const y = -row * height const percent = (i / frames) * 100 steps.push(`${percent}%{background-position:${x}px ${y}px}`) } return `@keyframes ${id}{${steps.join("")}}` }