How to achieve top-notch scrolling performance using HTML5 Canvas

Nick - Aug 5 '21 - - Dev Community

Intro

Nowadays, more and more companies adopt Canvas API to implement new shiny things inside a web browser.
Recently Google announced that Google Docs will now use a canvas based rendering. The SIP3 team decided to keep up as well.

Problem

If you use canvas for something ambitious, there is a great chance that you will run into performance issues. I bet you are wondering how I knew. Turns out large canvases are slow, especially on energy-efficient Safari, and may even cause significant energy or significant memory alerts.

That’s exactly the issue we faced while working on the highly interactive call flow interface at SIP3.

Call Flow

Solution

So we end up creating our personal set of rules to effectively address canvas performance challenges.

1. Do not work with Canvas API directly

Either you use a frontend framework or vanilla JS/TS take a look at one of these wonderful libraries.
Our choice is awesome Konva.js with react-konva bindings. It abstracts away low-level details, like redrawing, bubbling or layering inside canvas.
Konva supports many useful shapes like Rect, Circle, Text, Arrow, that you can use out of the box to construct UI.
So instead of imperative low-level code:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'coral';
ctx.fillRect(0, 0, 200, 200);
Enter fullscreen mode Exit fullscreen mode

You can use declarative React components.

<Rect x={0} y={0} width={200} height={200} fill='coral' />
Enter fullscreen mode Exit fullscreen mode

Konva has built-in events support, through React-style onEventName API.

<Text x={0} y={0} width={50} fontSize={10} text='Click me' onClick={() => console.log('You clicked me')} />
Enter fullscreen mode Exit fullscreen mode

Also Konva supports components nesting, like regular React components.

<Layer>
    <Text text='Text on canvas' fontSize={15} />
    <Group>
        <Circle x={200} y={100} radius={50} fill='green' />
        <Line x={20} y={200} points={[0, 0, 100, 0, 100, 100]} closed stroke='black' />
    </Group>
</Layer>
Enter fullscreen mode Exit fullscreen mode

2. Do not make your canvas too big

Try to avoid creating a canvas that is larger than your screen, because anything more than that will probably drop performance in the long run.

<Stage width={window.innerWidth} height={window.innerHeight}>
  <Layer>
    {/* Your shapes here are at most the size of your screen */}
  </Layer>
</Stage>
Enter fullscreen mode Exit fullscreen mode

Restricting stage size offers great performance gains because browser engine does not move large amounts of bytes from memory to screen on each redraw. Check out the Stage Section in Performance tips list for more detailed explanation.

3. Do not redraw static shapes

Create a separate canvas, where all static shapes would be drawn and place this canvas underneath the main one.

<Stage>
  {/* Layer for static shapes (e.g. background) */}
  <Layer>
    <Rect />
  </Layer>

  {/* Layer for interactive shapes, that react to user interactions */}
  <Layer>
    <Text />
    <Line />
    <Circle />
  </Layer>
</Stage>
Enter fullscreen mode Exit fullscreen mode

It massively improves performance, because Konva internally creates separate canvas for each Layer and refreshes only a layer that changed. Check out the Layer Management section in the docs for more info.

4. Do not rely on native scrolling!

Emulate the scrollbars in case you need a scene bigger than your canvas real size.

This rule is probably the most important out there, just look at the examples.

The first one uses native scrolling and causes the performance lag on every scroll event.


However, the identical app with emulated scrollbars works perfectly fine.

Instead of conclusion

I guess the canvas-based rendering will spread in the nearest future. Lots of web-applications will eventually switch from the good old HTML elements to painting pixels on the canvas.

Some tools, like Flutter are already rendering all user interface inside canvas.

So the earlier you accept this trend the better. And I just hope these tips will save you time when profiling and tweaking canvas performance.

Happy canvasing...

. . . . . . . .
Terabox Video Player