Drawing in a canvas

The <canvas> is a HTML tag that is used for drawing graphics through JavaScript. It has a lot of possibilities for drawing and animating shapes, images, and lines. There is a very decent tutorial that outlines some of the possibilities of this element in MDN: Canvas API Tutorial

In this case, we've used hooks to capture mouse events in the canvas (mousedown, mousemove, mouseout...) and update the canvas accordingly. As a result, everytime the user drags the mouse around the canvas, a line is drawn following the mouse, much like in a school's blackboard (which was the inspiration for this component).

This particular canvas contains a few tiny extras, like the eraser button (right of the color palette), the clear button (which simply calls clearRect on the canvas), and the download button, which creates a JPG image of the canvas and then downloads it to the user's device. However, I'm only going to go over the method for freestyle drawing in the canvas.

I'm using a few useState to store details about the canvas, but the only relevant for drawing freestyle are:

const [brush, setBrush] = useState({ color: "#f2f2f2" })
const [position, setPosition] = useState({ x: 0, y: 0 })

Then, when the user moves through the canvas element, I capture the move event, and draw a line in the canvas. It's important to keep track of the previous mouse position, hence, the call to setPosition.

The listener extracts the position of the move event, and then calculates where within the canvas the mouse was moved to. That information, combined with the previous mouse position (which have we conveniently set in a useState hook), is sufficient to draw a line in the canvas following the mouse. I've also saved the color in the state, so we have to set that property as well, before actually drawing.

After drawing, it's important to update the position of the mouse, so that the next line to be drawn starts where the current line finishes.

const handleMouseMove = e=> {
  const { clientX, clientY } = e
  const { x, y } = canvas.getBoundingClientRect()
  const endX = clientX - x
  const endY = clientY - y
  const context = canvas.getContext("2d")
  context.beginPath()
  context.moveTo(position.x, position.y)
  context.lineTo(endX, endY)
  context.globalCompositeOperation = "source-over"
  context.strokeStyle = brush.color
  context.stroke()
  context.closePath()
  setPosition({ x: endX, y: endY })
}

Note that the move event fires way too often and, since we are both drawing and updating React's state, it's probably wise to add a bit of throttling to limit the number of times we capture the event.

Also, I use the onMouseDown and the onMouseUp and similar events to activate and deactivate the drawing. I've omitted this from the code fragment above, but it's basically an on/off switch, so that unless you are actively pressing the mouse, nothing gets drawn.

More concepts