The Day `border-radius` killed our Canvas Performance
At Infinitas Learning we’re building e-learning platforms used every day by teachers and students for lessons, assignments, and collaboration. One of its core tools is a collaborative whiteboard, nothing particularly exotic: a <canvas>, pointer events, and a rendering loop that draws strokes as users move across the screen.
On desktop, it felt smooth. On modern tablets, it felt smooth too.
Then we tried it on a digital board in our office.
These devices are widely used in schools, and we can’t afford ignoring them. They are usually Android machines with large, high-resolution touchscreens, aging hardware, and vendor-specific system layers. In other words: exactly the kind of environment where small rendering inefficiencies become very visible.
On Firefox, our whiteboard dropped to 13 FPS.
Drawing a simple line felt like dragging a brush through molasses.
We could not get deep profiling data from the device, so at first we only had behavior to work with: FPS was low, the drawing code had not recently changed, and other canvas-based apps on the same hardware felt much smoother. That was enough to start isolating variables.
Start with the usual suspects
When canvas performance collapses, the obvious causes are usually things like:
- redrawing too much of the canvas every frame
- allocating objects inside the pointer loop
- scaling the canvas too aggressively with
devicePixelRatio - issuing too many draw calls
- doing expensive work in event handlers
We checked all of them.
Nothing stood out. The rendering loop was lightweight, and the canvas code looked healthy enough.
So we reduced the page to the minimum possible setup: just the whiteboard, no layout chrome, no surrounding UI, no decorative effects. In that stripped-down version, drawing went back to a stable 60 FPS.
That told us the bottleneck was probably not the drawing logic itself. It was something around it.
From there, we removed pieces one by one.
Some UI elements? No difference.
Shadows and visual effects? No difference.
Then we removed one CSS detail from the container.
.whiteboard-container {
/* ... */
border-radius: 12px;
}
Suddenly the whiteboard was smooth again.
Same device. Same browser. Same canvas code.
Just no rounded corners.
That was the key discovery.
We found the issue by removing border-radius. The rest of the investigation was about understanding why that visual change had such a large performance impact.
Why that result was surprising
At first, this looked absurd. border-radius is a visual property. It does not change our drawing code, our event handlers, or the number of pixels we ask the canvas API to paint.
But the browser does not only execute our JavaScript. It also has to decide how to paint, clip, rasterize, and composite the result on screen. That is where seemingly harmless CSS can become expensive.
So the article still starts with border-radius, because that is how we discovered the problem in practice. But the more precise explanation is that border-radius was part of a rounded clipping strategy around content that was updating continuously. In practice, that often means a combination such as border-radius plus overflow: hidden on the canvas container.
A simplified model of what the browser is doing
At a high level, browsers go through a familiar pipeline:
- parse HTML and CSS
- build the structures needed for rendering
- compute layout
- paint visual content
- composite the result for display
That last stage is the important one here.
Modern engines often try to keep expensive content isolated so it can be updated and displayed efficiently. A <canvas> is a good candidate for that, because it changes frequently and benefits from staying on a fast rendering path.
This is a simplification, and the exact implementation varies by browser and platform, but the healthy case often looks roughly like this:
JavaScript draws into the canvas
↓
the canvas surface updates
↓
the browser composites that result onto the page
When everything goes well, the browser can update the canvas and present it without doing much extra work around it.
For a broader explanation of rendering, paint, and compositing, MDN has a useful overview in How browsers work.
What border-radius was really doing
Once the canvas sits inside a rounded container that also clips its contents, the browser has an extra constraint: pixels from the canvas must not appear outside that rounded shape.
In code, that often looks like this:
.whiteboard-container {
border-radius: 12px;
overflow: hidden;
}
Now the browser may need additional work beyond simply presenting the updated canvas surface. Depending on the engine, the device, and the graphics stack, that can mean extra clipping, offscreen surfaces, masking, repainting, or compositing steps.
In other words, removing border-radius fixed the symptom we could see. The likely reason is that it also removed a rounded border-clipping path the browser had to respect while the canvas was updating.
A simplified version of the more expensive path looks like this:
JavaScript draws into the canvas
↓
the canvas surface updates
↓
the browser applies rounded clipping to the result
↓
the clipped result is composited on screen
That does not prove one specific internal fallback on every browser. It does explain why a purely visual property can still affect performance: it changes the rendering constraints around the canvas.
Why whiteboards expose the problem so clearly
A whiteboard is close to a worst-case scenario for this kind of issue because it updates continuously while the user is drawing.
The loop looks something like this:
pointermove
↓
draw stroke segment
↓
canvas updates
↓
browser presents the new frame
If rounded clipping adds extra work around each update, that cost shows up on every frame while the pointer is moving. On a modern device, that overhead may be barely noticeable. On older Android hardware, it can be the difference between a responsive tool and an unusable one.
Canvas size makes the effect worse.
Many whiteboards scale the backing surface for high-DPI screens:
CSS size: 800 × 600
devicePixelRatio: 3
actual canvas: 2400 × 1800
That is more than 4 million pixels. If the browser has to do additional clipping or compositing work on a surface that large, frame time can climb very quickly.
What we can say with confidence
From our testing, we can say this confidently:
- performance was poor on an older Android-based digital board running Firefox
- the canvas logic itself was not enough to explain the slowdown
- removing
border-radiusfrom the container restored smooth drawing
What we cannot prove from this debugging session alone is the exact internal path the browser took. Without profiler traces or a reduced benchmark across multiple browser versions, claims like “this definitely fell back to software clipping” would be stronger than our evidence.
So the most accurate version of the finding is this: we discovered the issue by removing border-radius, and the best explanation is that it had been triggering a rounded clipping strategy around a frequently updating canvas.
We also have a plausible hypothesis for why the behavior differed across devices. On the older whiteboard, Firefox may not have been able to keep that clipping path on a fast GPU path, which would make each canvas update more expensive. On another whiteboard with similar hardware but newer software running Chrome, the same kind of clipping appeared to be much cheaper. That does not prove a Firefox-specific software fallback, but it is consistent with differences in browser engines, graphics stacks, and hardware acceleration support.
The practical fix
The simplest fix, for now, was to stop clipping the canvas itself.
So at the moment our whiteboard is just square. We removed the rounded corners from the canvas container and kept the fast path.
That is not necessarily the final design solution, but it is the correct trade-off while performance is the more important constraint.
We also kept one additional optimization in mind: cap the pixel ratio when necessary.
const dpr = Math.min(window.devicePixelRatio, 2)
On older devices, that small compromise can reduce surface size significantly while keeping the visual quality good enough for real use.
The takeaway
Performance bugs do not always live inside the hot path you wrote.
Sometimes they live in the interaction between that hot path and the browser’s rendering pipeline.
In our case, the lesson was not “never use border-radius.” It was this: if removing border-radius suddenly fixes performance, the real problem may be the rounded border-clipping strategy attached to that element.
It was also a reminder never to underestimate CSS. A property that looks purely decorative can still change the rendering path enough to make a responsive canvas feel broken.
And it was a reminder not to underestimate the gap between one browser and another. Rendering is a deeply complex problem, and every browser-device combination brings its own engine behavior, graphics stack, and set of optimizations, or missing optimizations. Those differences can have an enormous impact on performance.
That is why the same UI can feel unusably slow on one setup and perfectly smooth on another. Older Firefox on Android may have taken a slower clipping path here, while a newer Chrome-based stack may have kept the same visual effect cheap enough to be almost invisible.
If you run into a whiteboard that performs well almost everywhere but struggles on older Android hardware, this is an easy experiment to try:
remove rounded clipping from the canvas container and see what happens.
The result may be surprisingly dramatic.
Written by