pota/use/animate

pota/use/animate swaps CSS classes (or part tokens) on an element and returns a promise that resolves when the resulting CSS animation finishes — or immediately, if no animation is running. Useful for chaining state changes after an enter / exit transition without watching animationend by hand. It also bundles a few animation utilities: cancel running animations, introspect @keyframes, and an owned requestAnimationFrame loop.

Exports

Examples

Await a class swap

animateClassTo swaps classes and resolves on animationend, so state changes can be chained without watching the event by hand. It resolves immediately when no animation runs, making it safe to await either way.

import { ref, render, signal } from 'pota'
import { animateClassTo } from 'pota/use/animate'

function App() {
	const box = ref()
	const state = signal('idle')

	const toggle = async () => {
		if (state.read() !== 'idle') return
		state.write('sliding out')
		// `.out` runs an animation — resolves on `animationend`
		await animateClassTo(box(), 'idle', 'out')
		state.write('snapping back')
		// `.idle` runs none — resolves immediately
		await animateClassTo(box(), 'out', 'idle')
		state.write('idle')
	}

	return (
		<>
			<style>{`
				.idle { background: #2a9d8f; }
				.out  { background: #e76f51; animation: slide .4s forwards; }
				@keyframes slide { to { transform: translateX(120px); } }
			`}</style>

			<button on:click={toggle}>animate</button>
			<div
				use:ref={box}
				class="idle"
				style={{
					width: '120px',
					padding: '1rem',
					color: 'white',
					'margin-top': '1rem',
				}}
			>
				state: {state.read}
			</div>
		</>
	)
}

render(App)

Drive a frame loop

useAnimationFrame returns { start, stop }, never starts on its own, and auto-stops on scope dispose. fn can call stop() to break the loop on the same tick.

import { render, signal } from 'pota'
import { useAnimationFrame } from 'pota/use/animate'

function App() {
	const frames = signal(0)

	const loop = useAnimationFrame(() => {
		frames.update(n => n + 1)
		if (frames.read() >= 100) loop.stop()
	})

	return (
		<>
			<button on:click={() => loop.start()}>start</button>
			<button on:click={() => loop.stop()}>stop</button>
			<p>frames: {frames.read}</p>
		</>
	)
}

render(App)