Slowmo: The Browser Time Bender You Didn't Know You Needed
Debugging animations is a special kind of frustration. You tweak a cubic-bezier curve, reload the page, and the entire transition blows past in 300 milliseconds. Was that easing right? Did the element overshoot? You squint, hit reload again, and try to catch it. Chrome DevTools lets you slow CSS animations to 25% or 10%, but that only covers CSS. Your requestAnimationFrame loops, your video playback, your GSAP timelines, and your Framer Motion transitions all keep running at full speed, completely out of sync.
slowmo takes a different approach. Instead of targeting one animation system, it patches every time-related API in the browser simultaneously. One function call -- slowmo(0.5) -- and everything slows to half speed: CSS animations, CSS transitions, videos, audio, requestAnimationFrame, performance.now(), Date.now(), setTimeout, setInterval, and any library that relies on them. Zero dependencies, zero configuration, and it works with GSAP, Three.js, Framer Motion, and canvas animations out of the box.
One Dial to Slow Them All
The core idea behind Slowmo is "virtual time." Rather than asking each animation framework to slow down individually, Slowmo intercepts the browser's own clock. When you call slowmo(0.1), timestamps from performance.now() advance at one-tenth their normal rate. Callbacks scheduled with setTimeout wait ten times longer. Video elements automatically adjust their playbackRate. CSS animations get their playback rate modified through the Web Animations API.
This means any library that reads time from the browser -- which is nearly all of them -- gets slowed down without knowing it. Three.js reads requestAnimationFrame timestamps. Framer Motion reads Date.now(). GSAP gets special treatment through its own globalTimeline.timeScale() API, which Slowmo auto-detects and adjusts.
Getting Started
Install with your package manager of choice:
npm install slowmo
yarn add slowmo
Then import and call it:
import slowmo from "slowmo";
slowmo(0.5);
That is the entire API for basic usage. Every animation on the page is now running at half speed.
The Speed Spectrum
The speed value is a multiplier where 1 is normal time:
slowmo(0); // pause everything
slowmo(0.1); // 10x slower -- perfect for frame-by-frame debugging
slowmo(0.5); // half speed
slowmo(1); // normal
slowmo(2); // double speed -- great for testing long animations
There are also convenience methods for common operations:
slowmo.pause(); // freeze time
slowmo.play(); // resume at previous speed
slowmo.reset(); // back to 1x
slowmo.getSpeed(); // read the current multiplier
Opting Elements Out
Sometimes you want the debug UI itself to run at normal speed while everything else crawls. Add the data-slowmo-exclude attribute to any element that should ignore the time warp:
<video data-slowmo-exclude src="debug-overlay.mp4" />
This is particularly useful for debug panels, timers, or reference videos that you want running in real time while inspecting a slowed-down animation beside them.
The Visual Dial
For interactive debugging sessions where you want to tweak speed on the fly, Slowmo ships with a visual dial component. There is a vanilla JavaScript version and a React version.
Vanilla JavaScript Dial
import { setupDial, shutdownDial } from "slowmo/dial";
setupDial();
// later, when done debugging
shutdownDial();
The dial appears as a draggable overlay on the page. Rotate it to adjust speed, click the center to toggle pause and play. It persists your speed preference in localStorage, so reloading the page keeps your settings.
React Dial Component
import { Slowmo } from "slowmo/react";
function App() {
return (
<div>
<MyAnimatedComponent />
<Slowmo />
</div>
);
}
Drop the <Slowmo /> component anywhere in your tree. It renders the same draggable dial with pause/play toggle, pointer-lock rotation for precise speed control, and localStorage persistence. No props required.
Patching the Matrix
Understanding what Slowmo patches helps you predict where it works and where it might not. Here is the full list of intercepted APIs:
// CSS Animations and Transitions
// Patched via Web Animations API playbackRate
// Video and Audio elements
// Patched via HTMLMediaElement.playbackRate
// requestAnimationFrame
// Timestamps are scaled to virtual time
// performance.now()
// Returns virtual time instead of real time
// Date.now()
// Returns virtual epoch time
// setTimeout and setInterval
// Delays are scaled by the inverse of the speed multiplier
// GSAP
// Auto-detected, uses gsap.globalTimeline.timeScale()
This coverage means that most animation libraries work automatically. Three.js scenes that read requestAnimationFrame timestamps slow down. Framer Motion components that reference Date.now() slow down. Canvas animations driven by requestAnimationFrame slow down. You do not need to configure integrations or install plugins.
Debugging a Complex Transition
Consider a multi-step page transition where elements fade, slide, and scale in a choreographed sequence:
import slowmo from "slowmo";
// Slow everything to 10% speed for inspection
slowmo(0.1);
// Now trigger your page navigation
// Every CSS transition, every rAF callback, every timed delay
// runs at one-tenth speed, perfectly synchronized
// When you spot the issue, pause to inspect
slowmo.pause();
// Fix the timing, then resume
slowmo.play();
// Done debugging, back to normal
slowmo.reset();
At 10% speed, a 300ms transition becomes a leisurely 3-second affair. You can watch every intermediate frame, spot easing problems, catch z-index conflicts during overlap, and verify that staggered delays produce the sequence you intended.
Integrating with Your Dev Workflow
A practical pattern is to conditionally load Slowmo only in development:
if (process.env.NODE_ENV === "development") {
import("slowmo").then(({ default: slowmo }) => {
// Expose to console for quick access
(window as any).slowmo = slowmo;
});
}
Now you can open the browser console and type slowmo(0.1) whenever you need to inspect an animation, without any UI changes or component modifications. For React projects, you can conditionally render the dial:
function DevTools() {
if (process.env.NODE_ENV !== "development") return null;
return <Slowmo />;
}
What Stays Outside the Time Warp
Slowmo is thorough but not omniscient. A few things fall outside its reach. Frame-based animations that increment a counter each frame rather than reading timestamps will not slow smoothly -- they will still tick once per frame, just with scaled timestamps available if they choose to read them. Libraries that cache references to performance.now or Date.now before Slowmo loads will read the original, unpatched functions. Load Slowmo first to avoid this.
Video and audio elements respect browser-imposed speed limits, typically ranging from about 0.0625x to 16x in Chrome. Content inside iframes requires Slowmo to be loaded separately inside each iframe. Web Workers, Service Workers, and Worklets run in their own threads and cannot be patched from the main thread. WebGL shaders with custom time uniforms need manual integration since they run on the GPU.
None of these are dealbreakers for the primary use case of debugging UI animations, which almost always live on the main thread and use standard browser APIs.
Conclusion
Slowmo solves a genuinely annoying problem with an elegant approach. Rather than building integrations for every animation library, it patches the browser's own sense of time and lets everything downstream adjust automatically. The API surface is minimal -- a single function and a handful of convenience methods. The React dial component makes interactive debugging sessions feel natural. And with zero dependencies and a laser focus on one job, it earns its place in a development toolkit without adding overhead to production builds.
The next time you find yourself mashing reload trying to catch a 200ms transition, reach for slowmo(0.1) instead. Your eyes will thank you.