useTicker

A hook for creating performant paint-dependent loops using requestAnimationFrame with precise timing and delta calculations

Source Code

import { useCallback, useEffect, useMemo, useRef } from "react";
 
type Ticker = {
  start: () => void;
  stop: () => void;
  paused: boolean;
};
 
type TickerCallback = (timestamp: number, delta: number) => void | boolean;
 
const MIN_DELTA = 0.000000001;
 
export const useTicker = (callback: TickerCallback): Ticker => {
  const rafId = useRef<number | null>(null);
  const previousTimestamp = useRef(0);
  const callbackRef = useRef(callback);
  const isRunning = useRef(false);
 
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);
 
  const tick = useCallback((timestamp: number) => {
    if (!isRunning.current) return;
 
    const delta = Math.max(MIN_DELTA, timestamp - previousTimestamp.current);
    const shouldContinue = callbackRef.current(timestamp, delta);
 
    previousTimestamp.current = timestamp;
 
    if (shouldContinue !== false) {
      rafId.current = window.requestAnimationFrame(tick);
    } else {
      isRunning.current = false;
      rafId.current = null;
    }
  }, []);
 
  const start = useCallback(() => {
    if (rafId.current) {
      window.cancelAnimationFrame(rafId.current);
    }
 
    isRunning.current = true;
    previousTimestamp.current = performance.now();
    rafId.current = window.requestAnimationFrame(tick);
  }, [tick]);
 
  const stop = useCallback(() => {
    if (rafId.current) {
      window.cancelAnimationFrame(rafId.current);
      rafId.current = null;
    }
 
    isRunning.current = false;
  }, []);
 
  useEffect(() => {
    return () => {
      stop();
    };
  }, [stop]);
 
  return useMemo(
    () => ({
      start,
      stop,
      get paused() {
        return !isRunning.current;
      },
    }),
    [start, stop]
  );
};

API Reference

PropDefaultTypeDescription

callback

*
-

(timestamp: number, delta: number) => void | boolean

The callback to call on each tick.

The hook returns an object with the following properties:

  • start: A function to start the ticker.
  • stop: A function to stop the ticker.
  • paused: A boolean indicating if the ticker is paused.

Examples

Looping Animation

About requestAnimationFrame

requestAnimationFrame is a browser API that schedules a callback to be executed before the next browser repaint. It's the recommended way to create animations and game loops as it automatically synchronizes with the browser's refresh rate.

You can read a more in-depth overview of requestAnimationFrame on our blog: Mastering JavaScript Web Animations.