useRaf
A composable for scope-disposed safe requestAnimationFrame with automatic cleanup.
Usage
The useRaf composable wraps requestAnimationFrame with a cancel-then-request pattern that deduplicates rapid calls. It returns a callable function that requests an animation frame, automatically canceling any pending frame.
<script setup lang="ts">
import { useRaf } from '@vuetify/v0'
import { ref } from 'vue'
const position = ref(0)
const updatePosition = useRaf((timestamp) => {
position.value = Math.sin(timestamp / 1000) * 100
})
// Call to request frame (cancels pending)
updatePosition()
// Manual cancel if needed
updatePosition.cancel()
// Check if frame is pending
console.log(updatePosition.isActive.value)
</script>Examples
Item 1
Scroll to see RAF throttling in action
Item 2
Scroll to see RAF throttling in action
Item 3
Scroll to see RAF throttling in action
Item 4
Scroll to see RAF throttling in action
Item 5
Scroll to see RAF throttling in action
Item 6
Scroll to see RAF throttling in action
Item 7
Scroll to see RAF throttling in action
Item 8
Scroll to see RAF throttling in action
Item 9
Scroll to see RAF throttling in action
Item 10
Scroll to see RAF throttling in action
Item 11
Scroll to see RAF throttling in action
Item 12
Scroll to see RAF throttling in action
Item 13
Scroll to see RAF throttling in action
Item 14
Scroll to see RAF throttling in action
Item 15
Scroll to see RAF throttling in action
Item 16
Scroll to see RAF throttling in action
Item 17
Scroll to see RAF throttling in action
Item 18
Scroll to see RAF throttling in action
Item 19
Scroll to see RAF throttling in action
Item 20
Scroll to see RAF throttling in action
Scroll rapidly - notice how RAF updates are throttled to one per frame
<script setup lang="ts">
import { useRaf } from '@vuetify/v0'
import { shallowRef, useTemplateRef } from 'vue'
const containerRef = useTemplateRef<HTMLElement>('container')
const scrollTop = shallowRef(0)
const scrollPercent = shallowRef(0)
const updateCount = shallowRef(0)
// useRaf deduplicates rapid scroll events to one update per frame
const updateScroll = useRaf(() => {
if (!containerRef.value) return
const el = containerRef.value
scrollTop.value = Math.round(el.scrollTop)
scrollPercent.value = Math.round(
(el.scrollTop / (el.scrollHeight - el.clientHeight)) * 100,
)
updateCount.value++
})
function onScroll () {
// Each scroll event triggers a RAF request
// Rapid events are deduplicated - only one callback per frame
updateScroll()
}
</script>
<template>
<div class="flex flex-col gap-4">
<div class="flex gap-4 text-sm">
<div class="px-3 py-1.5 rounded bg-surface-variant text-on-surface-variant">
Scroll: <span class="font-mono">{{ scrollTop }}px</span>
</div>
<div class="px-3 py-1.5 rounded bg-surface-variant text-on-surface-variant">
Progress: <span class="font-mono">{{ scrollPercent }}%</span>
</div>
<div class="px-3 py-1.5 rounded bg-primary text-on-primary">
RAF updates: <span class="font-mono">{{ updateCount }}</span>
</div>
</div>
<!-- Progress bar -->
<div class="h-2 bg-surface-variant rounded-full overflow-hidden">
<div
class="h-full bg-primary transition-all duration-75"
:style="{ width: `${scrollPercent}%` }"
/>
</div>
<div
ref="container"
class="h-48 overflow-y-auto border border-divider rounded-lg"
@scroll="onScroll"
>
<div class="p-4 space-y-4">
<div
v-for="i in 20"
:key="i"
class="p-4 rounded bg-surface-variant"
>
<p class="font-medium text-on-surface">Item {{ i }}</p>
<p class="text-sm text-on-surface-variant">
Scroll to see RAF throttling in action
</p>
</div>
</div>
</div>
<p class="text-xs text-on-surface-variant text-center">
Scroll rapidly - notice how RAF updates are throttled to one per frame
</p>
</div>
</template>
Key Features
Cancel-Then-Request Pattern
Each call cancels any pending frame before requesting a new one. This deduplicates rapid calls, ensuring only the latest request executes:
const update = useRaf(() => {
// This callback only runs once per frame
})
// These rapid calls result in only ONE frame callback
update()
update()
update()Automatic Cleanup
The composable automatically cancels pending frames when the Vue scope is disposed (component unmount, effect scope stop):
// No manual cleanup needed - handled automatically
const update = useRaf(callback)SSR Safe
The composable is a no-op in non-browser environments. isActive always returns false during SSR.
Architecture
useRaf provides a lightweight wrapper around requestAnimationFrame:
Functions
useRaf
(callback: (timestamp: DOMHighResTimeStamp) => void) => UseRafReturnScope-disposed safe requestAnimationFrame.