How to achieve top-notch scrolling performance using HTML5 Canvas
Tags: sip3, canvas, javascript, react
Published: 2021-08-05 08:13:59

## Intro
Nowadays, more and more companies adopt Canvas API to implement new shiny things inside a web browser.
Recently Google [announced](https://workspaceupdates.googleblog.com/2021/05/Google-Docs-Canvas-Based-Rendering-Update.html) 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](https://sip3.io/).

![Call Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6stc7urj0sjhnzx62l4t.gif)

## 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](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API#libraries).
Our choice is awesome [Konva.js](https://konvajs.org/) with [react-konva](https://konvajs.org/docs/react/Intro.html) 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:
```javascript
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'coral';
ctx.fillRect(0, 0, 200, 200);
```
You can use declarative React components.
```jsx
<Rect x={0} y={0} width={200} height={200} fill='coral' />
```
Konva has built-in events support, through React-style onEventName API.
```javascript
<Text x={0} y={0} width={50} fontSize={10} text='Click me' onClick={() => console.log('You clicked me')} />
```
Also Konva supports components nesting, like regular React components.
```jsx
<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>
```

### 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.
```jsx
<Stage width={window.innerWidth} height={window.innerHeight}>
  <Layer>
    {/* Your shapes here are at most the size of your screen */}
  </Layer>
</Stage>
```
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](https://konvajs.org/docs/performance/All_Performance_Tips.html) 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.
```jsx
<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>
```
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](https://konvajs.org/docs/performance/Layer_Management.html) 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.

{% codepen https://codepen.io/fromaline/pen/oNZKQep %}
However, the identical app with emulated scrollbars works perfectly fine.

{% codepen https://codepen.io/fromaline/pen/yLMmGvP %}

## 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](https://flutter.dev/docs/development/tools/web-renderers).

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...