useAnimationFrame
useAnimationFrame
runs a callback once every animation frame, providing direct access to the browser’s animation loop.
<script>
import { useAnimationFrame } from '@humanspeak/svelte-motion'
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
element.style.transform = `rotateY(${time / 10}deg)`
})
})
</script>
<div bind:this={element}>Rotating content</div>
<script>
import { useAnimationFrame } from '@humanspeak/svelte-motion'
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
element.style.transform = `rotateY(${time / 10}deg)`
})
})
</script>
<div bind:this={element}>Rotating content</div>
Usage
The callback receives a DOMHighResTimeStamp
representing the time elapsed since the time origin, in milliseconds.
<script lang="ts">
import { useAnimationFrame } from '@humanspeak/svelte-motion'
let cubeRef: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!cubeRef) return
const rotate = Math.sin(time / 10000) * 200
const y = (1 + Math.sin(time / 1000)) * -50
cubeRef.style.transform =
`translateY(${y}px) rotateX(${rotate}deg) rotateY(${rotate}deg)`
})
})
</script>
<div bind:this={cubeRef}>
Animated content
</div>
<script lang="ts">
import { useAnimationFrame } from '@humanspeak/svelte-motion'
let cubeRef: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!cubeRef) return
const rotate = Math.sin(time / 10000) * 200
const y = (1 + Math.sin(time / 1000)) * -50
cubeRef.style.transform =
`translateY(${y}px) rotateX(${rotate}deg) rotateY(${rotate}deg)`
})
})
</script>
<div bind:this={cubeRef}>
Animated content
</div>
How it works
useAnimationFrame
provides a simple way to create time-based animations by:
- Starting a
requestAnimationFrame
loop when called - Calling your callback with the current timestamp on each frame
- Returning a cleanup function that stops the animation loop
With $effect
For Svelte 5, wrap useAnimationFrame
in a $effect
and return its cleanup function. This ensures the animation loop is properly stopped when the component unmounts:
<script>
let element: HTMLDivElement
$effect(() => {
// Start animation loop
return useAnimationFrame((time) => {
// Your animation logic
if (!element) return
element.style.opacity = String(Math.sin(time / 1000))
})
// Cleanup happens automatically when effect reruns or component unmounts
})
</script>
<script>
let element: HTMLDivElement
$effect(() => {
// Start animation loop
return useAnimationFrame((time) => {
// Your animation logic
if (!element) return
element.style.opacity = String(Math.sin(time / 1000))
})
// Cleanup happens automatically when effect reruns or component unmounts
})
</script>
Performance
useAnimationFrame
is optimized for performance:
- Native timing: Uses
requestAnimationFrame
which runs at the optimal frame rate (typically 60 FPS) - Automatic cleanup: Cancels animation frames when the effect is destroyed
- SSR-safe: Returns a no-op function in server-side rendering environments
Common patterns
Smooth rotation
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const degrees = (time / 10) % 360
element.style.transform = `rotate(${degrees}deg)`
})
})
</script>
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const degrees = (time / 10) % 360
element.style.transform = `rotate(${degrees}deg)`
})
})
</script>
Pulsing animation
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const scale = 1 + Math.sin(time / 500) * 0.1
element.style.transform = `scale(${scale})`
})
})
</script>
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const scale = 1 + Math.sin(time / 500) * 0.1
element.style.transform = `scale(${scale})`
})
})
</script>
Oscillating movement
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const x = Math.sin(time / 1000) * 100
const y = Math.cos(time / 1500) * 50
element.style.transform = `translate(${x}px, ${y}px)`
})
})
</script>
<script>
let element: HTMLDivElement
$effect(() => {
return useAnimationFrame((time) => {
if (!element) return
const x = Math.sin(time / 1000) * 100
const y = Math.cos(time / 1500) * 50
element.style.transform = `translate(${x}px, ${y}px)`
})
})
</script>
API Reference
Parameters
The callback function receives one argument:
- time
number
- The total milliseconds since the animation started (DOMHighResTimeStamp)
Returns
A cleanup function () => void
that stops the animation loop when called.
When to use
Use useAnimationFrame
when you need:
- Frame-by-frame control over animations
- Time-based calculations for smooth motion
- Complex animation logic that depends on elapsed time
- Direct DOM manipulation for performance-critical animations
For declarative animations, consider using the motion
component with animate
props instead.
See also
- useTime - For reactive time stores
- Motion component - For declarative animations
- Examples - See useAnimationFrame in action
Based on Motion’s useAnimationFrame API.