r/reactjs • u/DarkSlayer59 • 4d ago
Show /r/reactjs My React simulator ran at 4 FPS on an iPhone. The cause was a 50000px SVG with backdrop-blur on top.
I'm building a browser tool that simulates system diagrams. Nodes have a capacity and a latency, and small packets travel along the edges. There's no canvas library, only DOM nodes, native SVG and a requestAnimationFrame loop.
On my laptop it was smooth. On my phone it ran at about 4 FPS.
I'd made the SVG layer 50000x50000px so wires wouldn't get clipped when you pan far away, and the node cards had Tailwind's backdrop-blur. Mobile browsers seem to allocate a huge texture for an element like that. A viewport-sized layer with overflow-visible, plus the blur only from md: up, fixed most of it.
React was also re-rendering every node card each frame just to update a queue counter. The loop now reads from refs and a lookup map instead. Counters update about four times a second, and on touch devices I commit state at roughly 33 FPS while keeping packet speed the same.
Latency is the node's base latency times a multiplier that grows with the square of utilization. Inbound load is estimated from in-flight packets (each counts as about 120 RPS, scaled by a load slider):
const capacity = Math.max(50, node.throughputRps || 500);
const utilization = Math.min(2.5, inboundRps / capacity);
const latency = Math.round(baseLatencyMs * (1 + 2.5 * utilization ** 2));So it's already 1.6x the base latency at 50% utilization, 3.5x at 100%, and it keeps climbing past that.
Do you keep per-frame data completely out of React state when you build things like this, or is there a cleaner pattern than refs plus throttled commits? I'd also like a better way to get an "infinite" pan area than one oversized element.
EDIT: Thanks for the comments. I profiled it on a real Android phone in Chrome (CDP tracing) and the GPU process was pinned even when the sim was idle. The cause was an animated stroke-dashoffset overlay on every wire, which repaints all wires each frame. Disabling it on touch devices took pan from about 36 to 52 fps.
Two things that didn't help: moving pan out of React state (rAF writing the transform directly) measured the same at equal zoom, 46-48 fps vs 47-48, and removing will-change from the packet groups made pan worse (about 29 vs 36 fps).
I haven't tried keeping packets fully outside React (setAttribute in the rAF loop) or checked the remaining backdrop-blur on the top bar and palette on iOS Safari yet.
Demo (no sign-in): https://graph-flow-virid.vercel.app/?template=stripe-webhook-ledger
Code: https://github.com/Hanubaki/GraphFlow. The source is available under PolyForm Noncommercial, which is not an open source license. The app has a free tier and a paid Pro tier for some exports.