200FPS: CSS vs Canvas vs WebGL vs WebGPU
August 01, 2026
Overview
A 200 FPS front-end produces a new frame approximately every 5 milliseconds. That is an extremely small budget: JavaScript, React work, style calculation, layout, painting, compositing and GPU rendering must collectively finish quickly enough for the next frame.
Producing 200 frames per second does not guarantee that users will see 200 distinct frames. The display must also refresh at 200 Hz or faster. A 60 Hz display can show at most 60 distinct updates per second, while a 240 Hz display can display up to 240. Browser animation callbacks scheduled with requestAnimationFrame() generally follow the active display refresh rate rather than an arbitrary target selected by the application.
Achieving high frame rates is therefore not about selecting WebGL by default. The right rendering approach depends on:
- how many objects change per frame
- whether the content must remain accessible and selectable
- whether the scene is two-dimensional or three-dimensional
- how much visual complexity exists
- which browsers and devices must be supported
- whether 200 FPS is a real product requirement or merely an interesting benchmark
What Does 200 FPS Mean?
Frame rate describes how frequently new images are produced. Frame time is the inverse: how long the application has to produce each frame.
| Target Frame Rate | Time Per Frame | Common Context |
|---|---|---|
| 60 FPS | 16.67 ms | Conventional desktop and mobile displays |
| 120 FPS | 8.33 ms | Modern phones, laptops and high-refresh displays |
| 144 FPS | 6.94 ms | Common gaming-monitor target |
| 165 FPS | 6.06 ms | Higher-refresh gaming displays |
| 200 FPS | 5.00 ms | Requires a 200 Hz-or-faster active display to show every frame |
| 240 FPS | 4.17 ms | High-end gaming monitors |
The 5 ms budget is not a 5 ms JavaScript budget. A browser may need to perform:
- JavaScript execution
- style recalculation
- layout
- paint
- layer compositing
- GPU command execution
- final presentation to the display
Changes to properties such as width, left, margins or font sizes can trigger layout and painting. Animating transform and opacity can often avoid those expensive stages and remain largely within the compositing portion of the pipeline.
At 200 FPS, it is sensible to target substantially less than 5 ms of measured application work. A frame that usually takes 4.9 ms has almost no protection against garbage collection, browser activity, input handling or occasional GPU stalls.
Frame Rate Is Limited by the Display
Rendering faster than the display refreshes does not make every generated frame visible.
For example:
Application output: 200 FPS
Display refresh: 120 Hz
Maximum visible: 120 distinct frames per secondThe browser normally invokes requestAnimationFrame() before a repaint and generally at a frequency corresponding to the display refresh rate. It also pauses or reduces callbacks in background tabs, making fixed “pixels per frame” animation incorrect.
A better design is:
- use
requestAnimationFrame() - calculate elapsed time from its timestamp
- move objects based on time, not the number of callbacks
- allow the browser to choose the presentation cadence
CSS and DOM Animation
CSS and the DOM should usually remain the first choice for ordinary interface animation.
They work especially well for:
- menus and drawers
- hover and focus effects
- page transitions
- cards and panels
- text and form controls
- a modest number of independently animated elements
The DOM preserves accessibility semantics, text selection, native event handling and responsive layout. CSS animations also let the browser optimize animation scheduling. Compositor-friendly animations based on transform and opacity can remain extremely efficient.
CSS Example
.moving-card {
animation: move-card 2s linear infinite;
will-change: transform;
}
@keyframes move-card {
from {
transform: translateX(0);
}
to {
transform: translateX(400px);
}
}will-change can help in carefully selected cases, but it should not be placed on every animated element. Extra compositor layers consume memory and carry their own management costs.
DOM Animation Trade-offs
| Strength | Limitation |
|---|---|
| Native accessibility and text support | Large numbers of changing elements increase style and layout work |
| Easy event handling and responsive layout | Animating geometric properties can trigger layout and paint |
| Simple implementation for UI motion | Not ideal for dense particle systems or highly dynamic scenes |
| Excellent for compositor-only transforms | Layer promotion can consume significant memory when overused |
SVG
SVG represents vector graphics through document elements such as paths, circles, lines and text.
It is often the right choice for:
- icons
- diagrams
- maps
- scalable illustrations
- interactive charts
- graphics requiring per-element events
- visuals that must remain crisp at any zoom level
Because SVG shapes remain structured elements, they are easier to inspect, style and interact with than pixels drawn into a canvas. Vector graphics also scale without becoming pixelated.
The trade-off is that a highly dynamic SVG with a very large number of independently changing elements can create substantial DOM, style and paint work. There is no universal object-count cutoff: complexity depends on path geometry, filters, clipping, animation type and hardware.
| Choose SVG When | Consider Canvas or GPU Rendering When |
|---|---|
| Individual shapes need events or semantic meaning | Thousands of objects change continuously |
| Resolution-independent vectors are important | The entire scene is redrawn every frame |
| The graphic contains text, labels and controls | Complex paths, filters or effects dominate frame time |
| The scene has moderate structural complexity | The scene behaves more like a game than a document |
Canvas 2D
Canvas 2D provides a bitmap drawing surface. Unlike SVG, it does not maintain a document element for each object that was drawn. Once a shape is painted, the canvas primarily contains pixels rather than an inspectable object model.
Canvas is often appropriate for:
- particle effects
- custom charts
- image editing
- simple games
- drawing applications
- dense two-dimensional animation
- scenes that are cheaper to redraw than to represent as DOM nodes
You become responsible for more of the system:
- clearing and redrawing the scene
- hit testing
- interaction regions
- accessibility alternatives
- scaling for device pixel ratio
- deciding which objects are dirty
Frame-Rate-Independent Canvas Animation
import { useEffect, useRef } from 'react';
export default function MovingBall() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d', { alpha: false });
let x = 20;
const velocity = 240; // CSS pixels per second
let previousTime = performance.now();
let frameId;
function frame(now) {
// Clamp large jumps after tab suspension or debugging pauses.
const deltaSeconds = Math.min((now - previousTime) / 1000, 0.05);
previousTime = now;
x += velocity * deltaSeconds;
if (x > canvas.width + 20) {
x = -20;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(x, canvas.height / 2, 16, 0, Math.PI * 2);
context.fill();
frameId = requestAnimationFrame(frame);
}
frameId = requestAnimationFrame(frame);
return () => cancelAnimationFrame(frameId);
}, []);
return <canvas ref={canvasRef} width={800} height={200} />;
}The movement is defined in pixels per second rather than pixels per callback. It therefore moves at approximately the same speed on 60 Hz, 144 Hz and 240 Hz displays.
MDN recommends requestAnimationFrame() for Canvas animation because the browser can schedule drawing around its repaint cycle.
React Is Not Limited to 60 FPS
React does not impose a 60 FPS ceiling.
The animation cadence comes from the browser, the display and the work performed per frame—not from React itself. A React application can participate in a 120 Hz, 144 Hz or 240 Hz animation loop.
The common mistake is using React state as the storage location for every per-frame position:
// Usually the wrong abstraction for a high-frequency animation loop.
setX(previousX => previousX + velocity * deltaSeconds);Every state update can cause React to schedule rendering, reconcile elements and commit DOM changes. That may be acceptable for a small component at ordinary frame rates, but it is often unnecessary overhead for a dense animation targeting a 5 ms budget.
useRef() stores mutable values between renders without triggering a React re-render when its .current value changes. React’s own documentation uses refs and requestAnimationFrame() for imperative animation patterns.
React, useRef() and requestAnimationFrame()
import { useEffect, useRef } from 'react';
export default function MovingDot() {
const elementRef = useRef(null);
const positionRef = useRef(0);
useEffect(() => {
const element = elementRef.current;
const velocity = 300;
let previousTime = performance.now();
let frameId;
function frame(now) {
const deltaSeconds = Math.min((now - previousTime) / 1000, 0.05);
previousTime = now;
positionRef.current =
(positionRef.current + velocity * deltaSeconds) % 600;
element.style.transform =
`translate3d(${positionRef.current}px, 0, 0)`;
frameId = requestAnimationFrame(frame);
}
frameId = requestAnimationFrame(frame);
return () => cancelAnimationFrame(frameId);
}, []);
return <div ref={elementRef} className="dot" />;
}React still owns:
- mounting and unmounting the element
- component composition
- controls and application state
- starting and cleaning up the animation loop
The ref-driven loop owns:
- per-frame mutable position
- timing
- direct compositor-friendly style updates
React State vs. Refs
| Data | Recommended Storage | Reason |
|---|---|---|
| Play / paused state | React state | The UI should re-render when it changes |
| Selected quality setting | React state | The setting affects visible controls and configuration |
| Object position every frame | useRef(), typed arrays or renderer-owned state | It changes frequently without requiring React reconciliation |
| Animation-frame request ID | useRef() or effect-local variable | It is mutable implementation state, not rendered UI state |
| Final score or completed result | React state | The application UI needs to display it |
This does not mean React state is slow or should never animate. It means the rendering abstraction should match the frequency and shape of the work.
WebGL
WebGL exposes GPU-accelerated two-dimensional and three-dimensional rendering through a <canvas> element. It is supported across modern browsers and has a mature ecosystem of engines, debugging tools, tutorials and production libraries.
WebGL is a strong choice for:
- complex particle systems
- games
- three-dimensional scenes
- image processing
- large numbers of sprites
- custom shaders
- simulations with substantial GPU-friendly parallel work
Its biggest performance advantage is not simply “the GPU is fast.” It lets an application:
- submit geometry in batches
- reuse buffers
- render many instances with relatively few draw calls
- perform per-vertex and per-pixel work in shaders
- avoid representing every visible item as a DOM node
Its trade-offs include:
- a stateful and relatively low-level API
- GLSL shader development
- manual resource lifecycle management
- context-loss handling
- more work for text, accessibility and hit testing
- CPU overhead when an application issues too many small draw calls
WebGL remains the established general-purpose GPU choice for the web because it combines broad browser support with years of production experience. That does not mean it is the correct choice for buttons, ordinary page transitions or a small animated chart.
WebGPU
WebGPU is the successor to WebGL. It maps more naturally to modern native GPU APIs, exposes both rendering and general-purpose compute, and uses explicit concepts such as command encoders, pipelines, bind groups and GPU buffers.
Potential advantages include:
- lower CPU overhead for complex workloads
- reusable command structures
- compute shaders
- more explicit resource and synchronization models
- better alignment with Direct3D 12, Metal and Vulkan-era hardware
- modern shader development through WGSL
Those benefits are most meaningful for substantial GPU workloads. A simple moving rectangle will not automatically become faster merely because it is rewritten in WebGPU.
WebGL vs. WebGPU
| Aspect | WebGL | WebGPU |
|---|---|---|
| Browser reach | Established across modern browsers | Broad and growing, but still affected by browser, OS and hardware gaps |
| API model | Stateful OpenGL ES-style model | Explicit modern GPU pipeline model |
| Shader language | GLSL | WGSL |
| Compute shaders | Not part of core WebGL | First-class compute support |
| Learning curve | Complex, but supported by mature learning material | More explicit setup and resource management |
| Ecosystem maturity | Long-established engines, libraries and tooling | Rapidly maturing ecosystem |
| Recommended role today | Broadly compatible production default for demanding graphics | Progressive enhancement or primary renderer where the supported audience is controlled |
Is WebGPU Production-Ready?
As of July 2026, WebGPU is available in current versions of the major browser families, but platform coverage remains uneven. Chromium support covers Windows, macOS, ChromeOS and supported Android devices; Firefox support still varies by operating system; and Safari support is tied to newer Apple operating-system releases.
Can I Use reports roughly 83.6% global browser support, combining full and partial support, based on June 2026 browser-usage data. That is substantial reach, but it is not universal coverage.
WebGPU is therefore production-ready under either of these conditions:
- the application targets a controlled browser and hardware environment
- WebGPU is used through progressive enhancement with a WebGL or Canvas fallback
It is not yet a good idea to assume WebGPU is universally available for a general-public website with no fallback. WebGPU also requires a secure HTTPS context.
A simple capability check looks like this:
async function selectRenderer(canvas) {
if ('gpu' in navigator) {
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
return createWebGPURenderer(canvas, adapter);
}
}
const webgl = canvas.getContext('webgl2');
if (webgl) {
return createWebGLRenderer(canvas, webgl);
}
return createCanvas2DRenderer(canvas);
}Capability detection is better than relying only on browser names. A browser may expose WebGPU on one operating system or GPU configuration but not another.
CSS vs. SVG vs. Canvas vs. WebGL vs. WebGPU
| Technology | Best For | Main Strength | Main Limitation |
|---|---|---|---|
| CSS / DOM | UI components, transitions, text and accessible controls | Native layout, semantics and interaction | Large changing element trees can trigger expensive browser work |
| SVG | Interactive vectors, diagrams, icons and charts | Scalable structured graphics with per-element events | Many frequently changing nodes can become expensive |
| Canvas 2D | Dense 2D drawing, particles, charts and image manipulation | Low DOM overhead and direct pixel drawing | Manual redraws, hit testing and accessibility |
| WebGL | Large dynamic scenes, 3D, sprites and GPU effects | Established GPU acceleration and broad support | Low-level API and substantial rendering complexity |
| WebGPU | Modern graphics, compute and CPU-heavy GPU submission workloads | Modern explicit GPU model and compute shaders | More limited platform reach and newer tooling |
The Most Important Optimization Techniques
Avoid Layout Work Inside the Frame Loop
Do not repeatedly measure and modify layout in an interleaved pattern:
// Bad: repeated layout reads and writes can force synchronization.
for (const element of elements) {
const width = element.offsetWidth;
element.style.left = `${width + 1}px`;
}Instead:
- measure outside the hot path or batch all reads
- cache dimensions
- batch writes afterward
- use
transformrather than layout-position properties where possible
const widths = elements.map(element => element.offsetWidth);
elements.forEach((element, index) => {
element.style.transform = `translateX(${widths[index] + 1}px)`;
});Properties affecting geometry can trigger style, layout and paint, while compositor-layer transforms can avoid much of that work.
Reduce Per-Frame Allocations
Avoid creating short-lived arrays and objects every frame:
// Creates a new object for every particle on every frame.
particles = particles.map(particle => ({
x: particle.x + particle.vx * delta,
y: particle.y + particle.vy * delta,
vx: particle.vx,
vy: particle.vy,
}));Prefer updating reusable storage:
for (let i = 0; i < particleCount; i += 1) {
positionsX[i] += velocitiesX[i] * delta;
positionsY[i] += velocitiesY[i] * delta;
}Typed arrays are especially useful when the data will later be uploaded to GPU buffers.
The goal is not “never allocate.” It is to avoid generating enough temporary garbage to introduce unpredictable collection pauses inside a 5 ms budget.
Batch GPU Work
For WebGL and WebGPU:
- combine objects into shared buffers
- use instanced rendering for repeated geometry
- reduce draw calls
- group objects by shader and texture
- use texture atlases where appropriate
- update contiguous buffer regions rather than many tiny buffers
- avoid recompiling shaders or rebuilding pipelines during animation
A thousand objects submitted in a few batches can be dramatically cheaper than a thousand individually configured draw calls.
Lower Internal Render Resolution
Rendering fewer pixels is often one of the highest-impact optimizations.
A full-screen canvas on a high-density 4K display can contain far more pixels than the CSS dimensions suggest. Canvas performance guidance recommends carefully managing the relationship between CSS size, backing-store size and scaling.
function resizeCanvas(canvas, context, renderScale = 0.75) {
const cssWidth = canvas.clientWidth;
const cssHeight = canvas.clientHeight;
const pixelRatio = Math.min(window.devicePixelRatio, 2);
const internalScale = pixelRatio * renderScale;
canvas.width = Math.round(cssWidth * internalScale);
canvas.height = Math.round(cssHeight * internalScale);
context.setTransform(
internalScale,
0,
0,
internalScale,
0,
0
);
}Possible quality modes:
Ultra: 1.00 × native internal resolution
High: 0.85 ×
Performance: 0.70 ×
Emergency: 0.50 ×Reducing each dimension to 70% lowers the total pixel count to roughly 49% of the original.
Use Adaptive Quality
Do not assume one quality level is appropriate for all hardware.
An adaptive renderer can monitor recent frame times:
const recentFrames = [];
function recordFrameTime(milliseconds) {
recentFrames.push(milliseconds);
if (recentFrames.length > 60) {
recentFrames.shift();
}
const average =
recentFrames.reduce((sum, value) => sum + value, 0) /
recentFrames.length;
if (average > 5) {
lowerRenderQuality();
} else if (average < 3.5) {
cautiouslyRaiseRenderQuality();
}
}Quality adjustments might include:
- lowering resolution
- reducing particles
- disabling expensive post-processing
- lowering shadow quality
- simplifying geometry
- reducing simulation frequency
Hitting a stable 160 FPS can provide a better experience than oscillating unpredictably between 240 and 90 FPS.
Use Workers and OffscreenCanvas
Web Workers move JavaScript work away from the main UI thread. OffscreenCanvas can transfer Canvas rendering to a worker in supported environments, allowing rendering or simulation work to continue without directly competing with DOM interaction on the main thread.
Main thread:
const canvas = document.querySelector('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('./renderer-worker.js', { type: 'module' });
worker.postMessage(
{
canvas: offscreen,
width: canvas.clientWidth,
height: canvas.clientHeight,
},
[offscreen]
);Worker:
self.onmessage = event => {
const { canvas, width, height } = event.data;
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
let x = 0;
let previousTime = performance.now();
function frame(now) {
const deltaSeconds = Math.min((now - previousTime) / 1000, 0.05);
previousTime = now;
x = (x + 250 * deltaSeconds) % width;
context.clearRect(0, 0, width, height);
context.fillRect(x, 50, 20, 20);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};Workers do not make computation free. Message passing, synchronization and data transfer have costs. They are most useful when substantial simulation, parsing or drawing work is blocking input and DOM responsiveness.
Avoid Fixed Timers for Animation
Do not use this as the main animation clock:
setInterval(update, 5);Problems include:
- timers do not synchronize with display presentation
- callbacks may be delayed by main-thread work
- background throttling changes timing
- multiple updates may occur without producing visible frames
- exact 5 ms scheduling is not guaranteed
Use requestAnimationFrame() for visible animation and elapsed time for simulation. For advanced games, simulation may run at a fixed timestep while rendering interpolates at the display cadence.
Measure the Real Bottleneck
A low frame rate can come from different stages:
| Symptom | Likely Bottleneck | Possible Response |
|---|---|---|
| Long JavaScript tasks | Simulation, React work, data processing or garbage collection | Reduce work, avoid allocations, split tasks or use a worker |
| Frequent forced layout | DOM measurement mixed with style writes | Batch reads and writes; cache geometry |
| Heavy paint time | Large painted areas, filters, shadows or complex paths | Use compositor properties or reduce visual complexity |
| High GPU time | Resolution, overdraw, shader complexity or excessive geometry | Lower resolution, simplify shaders, reduce overdraw |
| High submission overhead | Too many GPU state changes or draw calls | Batch, instance and reuse pipelines or buffers |
| Fast benchmark but slow interaction | Main thread monopolized by animation | Leave headroom for input; move work off-thread |
Browser performance tools can expose frame timing, layout, paint and scripting costs. Measure production builds on representative hardware rather than trusting a synthetic desktop benchmark.
How Many Users Can Actually Display 200 FPS?
There is no reliable public global dataset that directly reports the active refresh-rate distribution of web users.
Available sources measure adjacent facts:
- desktop devices represented roughly 47.2% of worldwide web usage in June 2026, with mobile representing approximately 51.5%
- Omdia projected 240 Hz panels to represent about 20% of the gaming-monitor market in 2026, but that is a specialized monitor segment and is not the global installed base of web displays
- a display capable of 240 Hz may still be configured to run at a lower active refresh rate
Clearly Labelled Estimate
A reasonable global planning estimate is:
Approximately 1–5% of general web users may currently have an active display capable of showing 200 distinct frames per second.
This is an inference, not a measured statistic.
The estimate assumes:
- most mobile and tablet traffic cannot display 200 distinct frames per second
- only part of desktop traffic uses gaming-class monitors
- only part of the gaming-monitor installed base supports at least 200 Hz
- shipment penetration is higher than installed-base penetration
- some capable displays are configured below their maximum refresh rate
For a gaming, graphics or enthusiast audience, the share could be substantially higher—perhaps 5–20%, depending on the product’s user base. Your own telemetry is more valuable than any global estimate.
Detecting Actual Callback Rate
You cannot reliably infer physical display capability from one browser property, but you can measure the observed requestAnimationFrame() cadence:
async function estimateRefreshRate(sampleCount = 120) {
const intervals = [];
let previous;
return new Promise(resolve => {
function frame(now) {
if (previous !== undefined) {
intervals.push(now - previous);
}
previous = now;
if (intervals.length < sampleCount) {
requestAnimationFrame(frame);
return;
}
intervals.sort((a, b) => a - b);
const median = intervals[Math.floor(intervals.length / 2)];
resolve(Math.round(1000 / median));
}
requestAnimationFrame(frame);
});
}
const estimatedHz = await estimateRefreshRate();
console.log(`Observed animation cadence: approximately ${estimatedHz} Hz`);This measurement is still imperfect. Browser throttling, variable refresh rate, power-saving modes, window placement and background activity can affect the result. Treat it as runtime telemetry, not a hardware guarantee.
Decision Guide
| Requirement | Recommended Starting Point | Move to Something Else When |
|---|---|---|
| Ordinary UI animation | CSS transforms and opacity | You need custom per-frame physics or many independently changing objects |
| Interactive vector diagram | SVG | The number or complexity of frequently redrawn elements becomes expensive |
| Dense custom 2D scene | Canvas 2D | CPU drawing or pixel fill becomes the bottleneck |
| Large particle or sprite scene | WebGL | Compute workloads or CPU submission overhead justify WebGPU |
| Cross-browser 3D production app | WebGL, often through a mature engine | Your audience has strong WebGPU coverage and modern GPU features provide measurable value |
| Modern GPU compute or advanced renderer | WebGPU with fallback | Nothing—provided you have measured benefits and acceptable support coverage |
| React-controlled animation | React for lifecycle and controls; refs or renderer state for each frame | State updates are infrequent enough that normal React rendering remains simpler |
Common Pitfalls
| Pitfall | Why It Hurts | Better Approach |
|---|---|---|
| Assuming React is capped at 60 FPS | Confuses framework rendering with display scheduling | Use the browser’s animation clock and minimize per-frame work |
| Updating React state every frame | May add unnecessary reconciliation and DOM commit work | Use refs, Canvas or renderer-owned mutable state for the hot loop |
| Using WebGL for ordinary UI | Adds complexity while losing native DOM semantics | Use CSS and DOM when they already meet the requirement |
| Assuming WebGPU is always faster | Small workloads may be dominated by setup and application logic | Benchmark the real scene against WebGL |
| Rendering at unrestricted device-pixel ratio | High-resolution displays multiply pixel workload | Cap pixel ratio or apply adaptive resolution scaling |
| Reading layout every frame | Can force synchronous style and layout work | Cache geometry and update it through resize or input events |
| Optimizing before profiling | Complexity is added without addressing the real bottleneck | Measure scripting, layout, paint and GPU time separately |
| Targeting 200 FPS for every user | Wastes power on displays that cannot show it | Adapt to observed refresh rate, hardware and user preference |
Accessibility and Power Consumption Still Matter
A 200 FPS animation is not automatically a good user experience.
High-frequency visual effects can:
- consume additional battery power
- heat mobile and laptop hardware
- compete with input and application work
- cause discomfort for motion-sensitive users
Respect prefers-reduced-motion, provide quality controls and pause nonessential animation when it is not visible. The browser already pauses many requestAnimationFrame() loops in hidden tabs, but applications should also stop unnecessary simulation and worker activity.
@media (prefers-reduced-motion: reduce) {
.nonessential-animation {
animation: none;
transition: none;
}
}Conclusion
A 200 FPS front-end is possible, but only on suitable displays and only when the complete frame pipeline consistently fits within a roughly 5 ms budget.
The best renderer depends on the workload:
- use CSS and DOM for normal interface motion
- use SVG for structured interactive vector graphics
- use Canvas 2D for dense custom two-dimensional drawing
- use WebGL for established, broadly supported GPU rendering
- use WebGPU when its modern GPU and compute model provides measurable value—and provide a fallback when serving the general web
React does not impose a 60 FPS limit. The important architectural choice is keeping high-frequency mutable animation state out of React’s normal render cycle when React does not need to reconcile that state.
Most importantly, do not optimize for an arbitrary number in isolation. Measure frame consistency, input responsiveness, power consumption, visual quality and the refresh rates of your actual users.
Key Takeaways
- 200 FPS provides a 5 ms frame budget
- users need a 200 Hz-or-faster active display to see every distinct frame
requestAnimationFrame()should drive visible animation- movement should be based on elapsed time, not frames
- React can support high-refresh animation, but per-frame state updates are often the wrong abstraction
- CSS transforms and opacity are usually best for ordinary UI
- Canvas is strong for dense custom 2D scenes
- WebGL remains the established cross-browser GPU choice
- WebGPU is production-ready for progressive enhancement or controlled audiences, but broad deployments still need a fallback
- a realistic global estimate for users who can display 200 FPS is currently in the low single digits, not a majority

