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 RateTime Per FrameCommon Context
60 FPS16.67 msConventional desktop and mobile displays
120 FPS8.33 msModern phones, laptops and high-refresh displays
144 FPS6.94 msCommon gaming-monitor target
165 FPS6.06 msHigher-refresh gaming displays
200 FPS5.00 msRequires a 200 Hz-or-faster active display to show every frame
240 FPS4.17 msHigh-end gaming monitors

The 5 ms budget is not a 5 ms JavaScript budget. A browser may need to perform:

  1. JavaScript execution
  2. style recalculation
  3. layout
  4. paint
  5. layer compositing
  6. GPU command execution
  7. 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 second

The 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

StrengthLimitation
Native accessibility and text supportLarge numbers of changing elements increase style and layout work
Easy event handling and responsive layoutAnimating geometric properties can trigger layout and paint
Simple implementation for UI motionNot ideal for dense particle systems or highly dynamic scenes
Excellent for compositor-only transformsLayer 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 WhenConsider Canvas or GPU Rendering When
Individual shapes need events or semantic meaningThousands of objects change continuously
Resolution-independent vectors are importantThe entire scene is redrawn every frame
The graphic contains text, labels and controlsComplex paths, filters or effects dominate frame time
The scene has moderate structural complexityThe 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

DataRecommended StorageReason
Play / paused stateReact stateThe UI should re-render when it changes
Selected quality settingReact stateThe setting affects visible controls and configuration
Object position every frameuseRef(), typed arrays or renderer-owned stateIt changes frequently without requiring React reconciliation
Animation-frame request IDuseRef() or effect-local variableIt is mutable implementation state, not rendered UI state
Final score or completed resultReact stateThe 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

AspectWebGLWebGPU
Browser reachEstablished across modern browsersBroad and growing, but still affected by browser, OS and hardware gaps
API modelStateful OpenGL ES-style modelExplicit modern GPU pipeline model
Shader languageGLSLWGSL
Compute shadersNot part of core WebGLFirst-class compute support
Learning curveComplex, but supported by mature learning materialMore explicit setup and resource management
Ecosystem maturityLong-established engines, libraries and toolingRapidly maturing ecosystem
Recommended role todayBroadly compatible production default for demanding graphicsProgressive 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

TechnologyBest ForMain StrengthMain Limitation
CSS / DOMUI components, transitions, text and accessible controlsNative layout, semantics and interactionLarge changing element trees can trigger expensive browser work
SVGInteractive vectors, diagrams, icons and chartsScalable structured graphics with per-element eventsMany frequently changing nodes can become expensive
Canvas 2DDense 2D drawing, particles, charts and image manipulationLow DOM overhead and direct pixel drawingManual redraws, hit testing and accessibility
WebGLLarge dynamic scenes, 3D, sprites and GPU effectsEstablished GPU acceleration and broad supportLow-level API and substantial rendering complexity
WebGPUModern graphics, compute and CPU-heavy GPU submission workloadsModern explicit GPU model and compute shadersMore 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:

  1. measure outside the hot path or batch all reads
  2. cache dimensions
  3. batch writes afterward
  4. use transform rather 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:

SymptomLikely BottleneckPossible Response
Long JavaScript tasksSimulation, React work, data processing or garbage collectionReduce work, avoid allocations, split tasks or use a worker
Frequent forced layoutDOM measurement mixed with style writesBatch reads and writes; cache geometry
Heavy paint timeLarge painted areas, filters, shadows or complex pathsUse compositor properties or reduce visual complexity
High GPU timeResolution, overdraw, shader complexity or excessive geometryLower resolution, simplify shaders, reduce overdraw
High submission overheadToo many GPU state changes or draw callsBatch, instance and reuse pipelines or buffers
Fast benchmark but slow interactionMain thread monopolized by animationLeave 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

RequirementRecommended Starting PointMove to Something Else When
Ordinary UI animationCSS transforms and opacityYou need custom per-frame physics or many independently changing objects
Interactive vector diagramSVGThe number or complexity of frequently redrawn elements becomes expensive
Dense custom 2D sceneCanvas 2DCPU drawing or pixel fill becomes the bottleneck
Large particle or sprite sceneWebGLCompute workloads or CPU submission overhead justify WebGPU
Cross-browser 3D production appWebGL, often through a mature engineYour audience has strong WebGPU coverage and modern GPU features provide measurable value
Modern GPU compute or advanced rendererWebGPU with fallbackNothing—provided you have measured benefits and acceptable support coverage
React-controlled animationReact for lifecycle and controls; refs or renderer state for each frameState updates are infrequent enough that normal React rendering remains simpler

Common Pitfalls

PitfallWhy It HurtsBetter Approach
Assuming React is capped at 60 FPSConfuses framework rendering with display schedulingUse the browser’s animation clock and minimize per-frame work
Updating React state every frameMay add unnecessary reconciliation and DOM commit workUse refs, Canvas or renderer-owned mutable state for the hot loop
Using WebGL for ordinary UIAdds complexity while losing native DOM semanticsUse CSS and DOM when they already meet the requirement
Assuming WebGPU is always fasterSmall workloads may be dominated by setup and application logicBenchmark the real scene against WebGL
Rendering at unrestricted device-pixel ratioHigh-resolution displays multiply pixel workloadCap pixel ratio or apply adaptive resolution scaling
Reading layout every frameCan force synchronous style and layout workCache geometry and update it through resize or input events
Optimizing before profilingComplexity is added without addressing the real bottleneckMeasure scripting, layout, paint and GPU time separately
Targeting 200 FPS for every userWastes power on displays that cannot show itAdapt 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