"time"
TEMPUS
One RAF loop to rule them all
A lightweight (~1KB) requestAnimationFrame orchestrator that merges all animation loops into one for optimal performance.
bun add tempusThe Problem
Modern creative websites often have multiple animation sources running simultaneously: smooth scroll, GSAP animations, Three.js render loops, and custom effects. Each creates its own requestAnimationFrame loop, causing:
Before Tempus
4 separate RAF callbacks = 4× overhead
With Tempus
1 RAF loop, 4 callbacks = optimal
Features
Tiny Size
~1KB gzipped. Zero dependencies. Does one thing extremely well.
Priority System
Control execution order with priorities. Critical animations run first.
Auto-cleanup
Callbacks are automatically removed when they return false.
Framework Agnostic
Works with any library: Lenis, GSAP, Three.js, vanilla JS.
Pause/Resume
Global control to pause all animations at once.
TypeScript
Full type definitions included.
Quick Start
Basic Usage
import Tempus from 'tempus'
// Add a callback to the global RAF loop
Tempus.add((time, deltaTime) => {
// time: total elapsed time
// deltaTime: time since last frame
console.log('Frame!', { time, deltaTime })
})
// Start the loop
Tempus.start()With Priority
// Higher priority = runs first
// Default priority is 0
Tempus.add((time, dt) => {
// Physics update (runs first)
}, 100)
Tempus.add((time, dt) => {
// Render (runs second)
}, 50)
Tempus.add((time, dt) => {
// UI updates (runs last)
}, 0)Auto-remove Callback
let frame = 0
Tempus.add((time, dt) => {
frame++
// Return false to remove this callback
if (frame >= 60) {
return false
}
})Integrations
With Lenis
import Lenis from 'lenis'
import Tempus from 'tempus'
const lenis = new Lenis()
// Add Lenis to Tempus instead of its own RAF
Tempus.add((time) => {
lenis.raf(time)
}, 100) // High priority for scroll
Tempus.start()With GSAP
import gsap from 'gsap'
import Tempus from 'tempus'
// Use Tempus as GSAP's ticker
gsap.ticker.remove(gsap.updateRoot)
Tempus.add((time) => {
gsap.updateRoot(time / 1000)
}, 50)
// Disable GSAP's internal lag smoothing
gsap.ticker.lagSmoothing(0)
Tempus.start()With Three.js
import * as THREE from 'three'
import Tempus from 'tempus'
const renderer = new THREE.WebGLRenderer()
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
// Add Three.js render to Tempus
Tempus.add((time, dt) => {
// Update animations
scene.rotation.y = time * 0.001
// Render
renderer.render(scene, camera)
}, 0) // Lower priority (renders after updates)
Tempus.start()Complete Stack
import Lenis from 'lenis'
import gsap from 'gsap'
import Tempus from 'tempus'
// Initialize
const lenis = new Lenis()
gsap.ticker.remove(gsap.updateRoot)
gsap.ticker.lagSmoothing(0)
// Priority order: Scroll → GSAP → Custom
Tempus.add((time) => lenis.raf(time), 100)
Tempus.add((time) => gsap.updateRoot(time / 1000), 50)
Tempus.add((time, dt) => {
// Your custom updates here
}, 0)
// Single start for everything
Tempus.start()API Reference
Methods
| Property | Type | Default | Description |
|---|---|---|---|
Tempus.add(callback, priority?) | (callback, priority?) => () => void | — | Add a callback. Returns unsubscribe function. |
Tempus.remove(callback) | (callback) => void | — | Remove a specific callback |
Tempus.start() | () => void | — | Start the RAF loop |
Tempus.stop() | () => void | — | Stop the RAF loop |
Tempus.time | number | — | Current elapsed time (read-only) |
Callback Signature
type TempusCallback = (
time: number, // Total elapsed time in ms
deltaTime: number // Time since last frame in ms
) => void | false // Return false to auto-removeWhy It Matters
Without Tempus
- Multiple RAF loops compete for resources
- Each library manages its own timing
- No guaranteed execution order
- Harder to pause/resume all animations
With Tempus
- Single RAF loop, minimal overhead
- Consistent timing across all systems
- Predictable priority-based execution
- Global pause/resume with one call