gsap.ticker
gsap.ticker is GSAP's central requestAnimationFrame loop, the single heartbeat that drives every tween, timeline, and ScrollTrigger update on the page. You hook your own per-frame code into it with `gsap.ticker.add()` so your function runs on the same clock GSAP already runs, instead of spinning up a second requestAnimationFrame loop that competes for the same frame.
Updated July 1, 2026
How gsap.ticker works
GSAP doesn't run one requestAnimationFrame loop per tween. It runs exactly one, internally, and every animation reads its time from that loop. gsap.ticker is the handle to it. When you call gsap.ticker.add(fn), your function joins the same tick, so anything you do per frame stays in lockstep with GSAP's own updates instead of drifting a frame ahead or behind.
The callback gets time, deltaTime, and frame. deltaTime is the one you usually want: multiply your movement by it (or normalize with deltaRatio, below) and the motion stays consistent whether the browser is running at 60, 120, or a throttled 30 frames per second.
Keeping everything on one loop is also a performance safety net. The HTTP Archive Web Almanac 2025 found non-composited, jank-prone animations running on 40% of mobile and 44% of desktop pages, and a stray second requestAnimationFrame loop fighting GSAP for the same frame is one of the ways you end up there. Source: [Web Almanac 2025, Performance chapter](https://almanac.httparchive.org/en/2025/performance).
You rarely need to add a callback
gsap.quickTo() already runs on the ticker under the hood and is simpler. Reach for gsap.ticker.add() when you own a loop that GSAP doesn't, physics, a canvas draw, a custom follower with per-frame logic, and want it on the same clock.fps, lagSmoothing, deltaRatio
Three controls decide how the loop behaves. All are global, they affect every GSAP animation on the page, not just your callback.
fps
gsap.ticker.fps(30) throttles the whole engine to 30 frames per second. Useful for a deliberately choppy, filmic look, or to cut battery draw on a heavy background loop. Set it back with gsap.ticker.fps(-1) to run as fast as the display allows (matching the browser's refresh rate).
lagSmoothing
When the main thread stalls, say a long task or a tab regaining focus, the gap between two ticks can be huge, and without protection every tween would jump forward by that whole gap. lagSmoothing caps that jump. The default is gsap.ticker.lagSmoothing(500, 33): if a frame takes longer than 500ms, GSAP pretends only 33ms passed so animations don't teleport. Call gsap.ticker.lagSmoothing(0) to disable it (real time, jumps and all), which you want if your animation must stay locked to an external clock like audio or video.
deltaRatio
gsap.ticker.deltaRatio(60) returns the ratio of the current frame's delta to a 60fps baseline. Multiply frame-based movement by it and a follower moves the same real-world distance per second on a 30Hz laptop and a 120Hz phone. Without it, a lerp like x += (target - x) * 0.1 moves twice as fast at 120fps as at 60fps.
Use gsap.ticker for
- A cursor follower or trail with per-frame logic beyond a single tween (velocity, rotation, decay)
- Driving a canvas or WebGL draw call in sync with GSAP tweens on the same page
- Physics or spring loops you wrote yourself that should share one frame with GSAP
- Reading
gsap.ticker.deltaRatio()to make frame-based movement refresh-rate independent - Throttling every animation to a lower
fpsfor a deliberate look or battery savings
Use something else when
- You're just easing one element toward a moving target, use
gsap.quickTo(), it already rides the ticker and is one line - You want a value to animate over a set duration, use a normal
gsap.to()tween, not a manual loop - The work is scroll-driven, use
ScrollTriggerwithscrub, it maps scroll position to progress without a per-frame callback - You only need a one-off
requestAnimationFrameunrelated to any GSAP animation, plainrequestAnimationFrameis fine and has no GSAP dependency
Used in these Annnimate components
The continuous, cursor-driven components are the ones that live on a per-frame loop, which is exactly what gsap.ticker exists to run:
- Custom Cursor runs a per-frame loop that tracks pointer velocity, tilts the label toward the direction of travel, and decays the rotation back to rest, the kind of continuous update
gsap.ticker.add()is built for - Image Trail spawns and fades trail images off pointer movement, per-frame work that reads cleanest on GSAP's single central clock rather than a second competing loop
Always remove your callback
gsap.ticker.add() runs forever until you call gsap.ticker.remove(fn). In React, remove it in the useGSAP cleanup (or a gsap.context revert); in a component that unmounts, a leaked ticker callback keeps firing against detached elements and quietly burns CPU.See it running in production
Common questions
- What's the difference between gsap.ticker.add and requestAnimationFrame?
gsap.ticker.add()runs your function on GSAP's single internal requestAnimationFrame loop, the same one every tween uses, so your code updates in the same frame as your animations. A rawrequestAnimationFramespins up a second loop that GSAP knows nothing about, so your update and GSAP's can land one frame apart, and you get none of the built-in extras (lagSmoothing, fps throttling, deltaRatio). If your per-frame code sits alongside GSAP animations, use the ticker.- Why is my animation frozen for a second after switching tabs?
- That's lagSmoothing doing its job. When a tab loses focus the browser pauses requestAnimationFrame, so the next tick after you return has a massive delta. GSAP's default
lagSmoothing(500, 33)clamps any gap over 500ms down to 33ms so tweens don't teleport to the end. It's not frozen, it just refuses to fast-forward through the time you were away. If you need real elapsed time (audio or video sync), callgsap.ticker.lagSmoothing(0). - How do I make cursor-follow movement the same speed on a 120Hz screen?
- Multiply your per-frame movement by
gsap.ticker.deltaRatio(60). A lerp likex += (target - x) * 0.1moves twice as far per second at 120fps as at 60fps because it runs twice as often.deltaRatio(60)returns ~0.5 at 120fps and ~2 at 30fps, so scaling by it lands the same real-world speed on any refresh rate. Or sidestep the whole problem withgsap.quickTo, which is duration-based and already frame-rate independent. - Do I need to remove a ticker callback when a component unmounts?
- Yes.
gsap.ticker.add()has no auto-cleanup, the function keeps firing every frame until you callgsap.ticker.remove(fn). In React put the remove call in theuseGSAPcleanup return, or add the callback inside agsap.context()and revert it. A leaked callback runs against elements that no longer exist and wastes a frame's worth of work on every tick. - Can I throttle GSAP to 30fps for a filmic look?
- Call
gsap.ticker.fps(30). It throttles the entire engine, every tween, timeline, and ScrollTrigger, not just your own callback, so use it deliberately. Reset withgsap.ticker.fps(-1)to run at the display's refresh rate again. For a choppy look on one element only, animatesnapon that tween instead of throttling the global ticker.
