Adding A Spray Tool To An HTML5 Canvas

JavaScript

19/06/2021


Adding a spray tool can seem complicated at first, but the logic behind it is relatively simple. A spray tool behaves very much like a pencil tool, but it differs in one simple way: 💁‍♂️ it adds additional random points around every point drawn.

Random dots around drawn dot

Drawn dot (with gray border)
surrounded by randomly generated dots

Change draw style

Before we work on the logic of the spray distribution, let's first create a dropdown that enables a user 👩‍💻 to switch between drawing styles:

  • Spray
  • Pencil
HTML
<select onchange="changeDrawStyle(this.value)">
<option value="pencil">Pencil</option>
<option value="spray">Spray</option>
</select>

I've added a function called changeDrawStyle that listens to any changes in the dropdown. And it has access to these changes through the argument this.value.

The function

What does the function do? Simple really! 🤓 We first track the current drawing style with a variable called drawStyle. The function then simply changes the value whenever a new style is selected.

JAVASCRIPT
let drawStyle = 'pencil'
const changeDrawStyle = (newStyle) => drawStyle = newStyle

Spray logic

The backbone of the spray logic is called getRandomOffset. Now don't worry! 😰 You don't really have to understand the function in depth if you don't want to. If you google "Random points generator", you'll find plenty of code snippets accomplishing the same thing.

JAVASCRIPT
function getRandomOffset(radius) {
const randomAngle = Math.random() * (2 * Math.PI);
const randomRadius = Math.random() * radius;
return {
x: Math.cos(randomAngle) * randomRadius,
y: Math.sin(randomRadius) * randomAngle
}
}

The only thing you should know is that it randomly generates a coordinate inside the shape of a circle ⭕️.

Applying the random points

For every point that we "draw" (which we are actually not), let's add 20 other points around it. We add the random coordinates to the "drawn" central point and then apply every random point to the canvas with fillRect.

JAVASCRIPT
const generateSprayPoints = (event) => {
const amountOfPoints = 20
for (let i = 0; i < amountOfPoints; i++) {
const offset = getRandomOffset(context.lineWidth * 2);
const x = event.clientX + offset.x
const y = event.clientY + offset.y
context.fillStyle = context.strokeStyle
context.fillRect(x, y, 1, 1)
}
}

Yes, by using fillRect, each point is technically 😛 a square. However, it's so small you cannot tell. Furthermore, the color of the "square" should always be equals to that of the app state, i.e. context.strokeStyle.

Another point I should mention is that getRandomOffset is dependent on our custom line width so that the spray "grows" with a larger line width. You could also apply this to the amount of generated points so that smaller sprays don't look as concentrated.

JAVASCRIPT
const amountOfPoints = context.lineWidth * 2

Checking for draw style

To wrap it up, in our draw function, we always check if we want to use the spray tool. If yes, quit the function and generate the points. As mentioned previously, we don't draw the center line with a spray, hence we skip the stroke method.

JAVASCRIPT
const draw = (event) => {
if (!isDrawing) return
context.lineTo(event.clientX, event.clientY)
if (drawStyle === 'spray') return generateSprayPoints(event) // new
context.stroke()
}

As usual, you can check out the demonstrated code in an actual example right here.


WRITTEN BY

Code and stuff