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