The C-Files: Art & Interfaces
By: Sean Donohue
Tags: Interfaces, React, Html5 Canvas, HTML, Whiteboarding

Building a Free-Draw interface in React with React Konva

I recently built a React app that features canvas. For any reader who is not familiar, a “canvas” is a type of HTML element that is extremely useful for rendering graphics and animations. Its corresponding API makes it possible for users to create a wide variety of images, and there are many existing libraries out there that simplify and build features on top of the core API.

In this tutorial, I will be focusing on one particular library, React-Konva, which is part of the larger Konva Framework.

React-Konva allows you to interact with a canvas element in a way that is cleaner and more intuitive to developers who are used to using React. I for one was struggling to design an interface with the vanilla canvas API that would allow the sort of flexibility that I was looking for, and this ended up being a fantastic tool for my case. The really awesome thing about using Konva, is that the components you can import from their library can take props, which will allow a degree of customization with much less than code than with other canvas libraries.

Before we get started building, I’ll go over the components you need in order to setup for free-drawing. First, we’ll need to create a react app in a fresh directory, then we need to install the library by using this command.

npm install react-konva konva — save

We’ll need to import the following from ‘react-konva’ in our App.js file.

Stage Layer Rect Line

So now our basic setup will look something like this…

But what is the use of each of these and how do they interact with the Canvas API?

A Stage is Konva’s abstraction of an HTML canvas element. It is in effect, the canvas that we will be working with, and it functions in a similar way to the base Canvas API.

A Layer is what functions as a “rendering context” for the stage. All canvas elements require a rendering context in order for a specific event to actually trigger visible changes on the canvas itself.

A Rect is a graphic that is a 2d rectangle. In our case, it used to setup a white backdrop in order for us to see changes against a different colored background style.

A Line is the component that makes a free-drawing app possible. Line’s in Konva require an array of points that are registered like

[x1, y1, x2, y2, x3, y3…]

…and lines require an attribute called tension, which controls how curvy the connection between points looks. The value will be between zero and one; a value closer to one results in a more curvy look, and a value closer to zero results in a more discrete look. Between two sets of coordinates, these changes will not be visible, but when plotting many tens or hundreds points in sequence, you will begin to see what a difference.

We are also going to utilize the useRef hook so that we can toggle drawing based on mouse events. We do not want to be using the useState hook for toggling here, because unlike the values for the state hook, the values that we store as refs will not change or be set to default across renders. AND when we change the value assigned to a useRef hook, it will not cause a page re-render. This is very important because drawing on the Stage is controlled by refs changing on mouse events. You probably should not use the state hook for this purpose because it will cause more re-renders than is necessary and we want to minimize this for performance.

Ok, so now that we have the let’s get started!

In order to get setup, we need to need make sure our components are rendered in the following order, before we start adding other features. The way Konva Stage and Layer components work is by wrapping other components. So the Stage will wrap a Layer (the canvas will grab its rendering context) and Layer will wrap the Rect and all of the Lines (the rendering context renders the canvas objects). Our stage will work like a regular canvas element, in that we need to give it width and height attributes. Since we are working against a darker backdrop, we are going to import and use a Rect component inside the layer that will fill close to the same dimensions as the canvas. This all happening inside the render portion of the App component.