Mastering the Fabric.js SprayBrush: From Basic Configuration to Advanced Customization
Introduction
The SprayBrush tool in Fabric.js is a versatile and engaging feature, offering a wide range of configurable properties that make it straightforward to use.
Basic Configuraton
To use the spray brush, the canvas must first be set too "drawing mode."
Set isDrawingMode to true to enable this mode.
<canvas id="canvasElement" width="500" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
// Initialize canvas with drawing mode enabled
const drawingCanvas = new fabric.Canvas('canvasElement', {
isDrawingMode: true
});
</script>
Drawing mode can also be enabled after initialization:
drawingCanvas.isDrawingMode = true;
To return to normal mode, simply set isDrawingMode back too false.
Registering the Spray Brush
The spray brush is implemented as SprayBrush.
When registernig, pass the initialized canvas to the brush constructor and assign it to canvas.freeDrawingBrush.
// Assign spray brush directly
drawingCanvas.freeDrawingBrush = new fabric.SprayBrush(drawingCanvas);
// Recommended approach: store brush in a variable for easier configuration
let sprayTool = new fabric.SprayBrush(drawingCanvas);
drawingCanvas.freeDrawingBrush = sprayTool;
Alternatively, you can use the initialize() method:
let sprayTool = new fabric.SprayBrush();
sprayTool.initialize(drawingCanvas);
drawingCanvas.freeDrawingBrush = sprayTool;
The initialize() method accepts the canvas instance as its parameter.
Setting Brush Width
To demonstrate other properties more clearly, let's first increase the brush width.
sprayTool.width = 200;
The width property controls the brush thickness—higher values create thicker sprays.
Configuring Spray Density
The density property determines the concentration of spray points. Higher values increase density.
Default density is 20.
sprayTool.width = 200;
sprayTool.density = 100; // Increase spray density
Compare with lower density:
sprayTool.width = 200;
sprayTool.density = 10;
The difference is visually apparent.
Adjusting Dot Size
Each individual point in the spray is called a "dot." The dotWidth property controls dot size.
Default dotWidth is 1. Larger values create bigger dots.
sprayTool.width = 200;
sprayTool.dotWidth = 10; // Increase dot size
Setting Dot Size Variance
The dotWidthVariance property introduces randomness in dot sizes within a specified range.
Default dotWidthVariance is 1. Higher values create greater size variation.
sprayTool.width = 200;
sprayTool.dotWidthVariance = 10;
When dotWidthVariance is set, dotWidth becomes less significant.
Preventing Overlap Removal
By default, the spray brush removes overlapping dots for performance reasons.
To disable this behavier, set optimizeOverlapping to false.
sprayTool.optimizeOverlapping = false;
Enabling Random Opacity
The randomOpacity property randomizes the opacity of spray dots.
sprayTool.randomOpacity = true;
Adding Shadow Effects
Although not explicitly documented for the spray brush, shadow effects can be applied similarly to basic brushes.
sprayTool.width = 200;
sprayTool.dotWidthVariance = 10;
// Apply shadow effect
sprayTool.shadow = new fabric.Shadow({
blur: 10,
offsetX: 10,
offsetY: 10,
color: '#30e3ca'
});
Setting Spray Color
The color property controls the spray color, though it's not well-documented.
sprayTool.color = 'pink';
Event Handling
Beyond the initialize() method, the spray brush provides several event handlers.
Before and After Path Creation
Since spray creates a path, you can listen to canvas path creation events.
before:path:created: Triggered before spray generationpath:created: Triggered after spray generation
// Before generation
drawingCanvas.on('before:path:created', event => {
console.log(event.path);
});
// After generation
drawingCanvas.on('path:created', event => {
console.log(event.path);
});
Mouse Down Event
sprayTool.onMouseDown = function(mousePosition) {
this.sprayChunks.length = 0;
this.canvas.clearContext(this.canvas.contextTop);
this._setShadow();
this.addSprayChunk(mousePosition);
this.render(this.sprayChunkPoints);
};
Mouse Move Event
sprayTool.onMouseMove = function(mousePosition) {
if (!this.limitedToCanvasSize || !this._isOutSideCanvas(mousePosition)) {
this.addSprayChunk(mousePosition);
this.render(this.sprayChunkPoints);
}
};
Mouse Up Event
sprayTool.onMouseUp = function() {
var originalRenderSetting = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false;
var sprayElements = [];
for (var chunkIndex = 0; chunkIndex < this.sprayChunks.length; chunkIndex++) {
for (var pointIndex = 0; pointIndex < this.sprayChunks[chunkIndex].length; pointIndex++) {
var currentPoint = this.sprayChunks[chunkIndex][pointIndex];
var sprayDot = new fabric.Rect({
width: currentPoint.width,
height: currentPoint.width,
left: currentPoint.x + 1,
top: currentPoint.y + 1,
originX: "center",
originY: "center",
fill: this.color
});
sprayElements.push(sprayDot);
}
}
if (this.optimizeOverlapping) {
sprayElements = this._getOptimizedRects(sprayElements);
}
var sprayGroup = new fabric.Group(sprayElements);
if (this.shadow) {
sprayGroup.set("shadow", new fabric.Shadow(this.shadow));
}
this.canvas.fire("before:path:created", { path: sprayGroup });
this.canvas.add(sprayGroup);
this.canvas.fire("path:created", { path: sprayGroup });
this.canvas.clearContext(this.canvas.contextTop);
this._resetShadow();
this.canvas.renderOnAddRemove = originalRenderSetting;
this.canvas.requestRenderAll();
};
Advanced Customization
Customizing Spray Dot Shapes
The key to customizing spray behavior lies in the onMouseUp event handler.
The default implementation uses Rect elements for spray dots. What if we use different shapes?
Let's try circles instead:
sprayTool.onMouseUp = function() {
var originalRenderSetting = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false;
var sprayElements = [];
for (var chunkIndex = 0; chunkIndex < this.sprayChunks.length; chunkIndex++) {
for (var pointIndex = 0; pointIndex < this.sprayChunks[chunkIndex].length; pointIndex++) {
var currentPoint = this.sprayChunks[chunkIndex][pointIndex];
// Use Circle instead of Rect
const sprayCircle = new fabric.Circle({
radius: currentPoint.width,
top: currentPoint.y + 1,
left: currentPoint.x + 1,
originX: "center",
originY: "center",
fill: this.color
});
sprayElements.push(sprayCircle);
}
}
if (this.optimizeOverlapping) {
sprayElements = this._getOptimizedRects(sprayElements);
}
var sprayGroup = new fabric.Group(sprayElements);
if (this.shadow) {
sprayGroup.set("shadow", new fabric.Shadow(this.shadow));
}
this.canvas.fire("before:path:created", { path: sprayGroup });
this.canvas.add(sprayGroup);
this.canvas.fire("path:created", { path: sprayGroup });
this.canvas.clearContext(this.canvas.contextTop);
this._resetShadow();
this.canvas.renderOnAddRemove = originalRenderSetting;
this.canvas.requestRenderAll();
};
This circular spray resembles the effect of the CircleBrush.
You can experiment with other Fabric.js shapes or even custom shapes by modifying the element creation in the onMouseUp handler.
Creating Random Color Spray
Building on the shape customization concept, creating a random color spray is straightforward—simply assign different colors to each dot.
sprayTool.onMouseUp = function() {
var originalRenderSetting = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false;
var sprayElements = [];
for (var chunkIndex = 0; chunkIndex < this.sprayChunks.length; chunkIndex++) {
for (var pointIndex = 0; pointIndex < this.sprayChunks[chunkIndex].length; pointIndex++) {
var currentPoint = this.sprayChunks[chunkIndex][pointIndex];
// Generate random RGB values for each dot
let red = Math.floor(Math.random() * 255);
let green = Math.floor(Math.random() * 255);
let blue = Math.floor(Math.random() * 255);
var sprayDot = new fabric.Rect({
width: currentPoint.width,
height: currentPoint.width,
left: currentPoint.x + 1,
top: currentPoint.y + 1,
originX: "center",
originY: "center",
fill: `rgb(${red}, ${green}, ${blue})` // Unique color for each dot
});
sprayElements.push(sprayDot);
}
}
if (this.optimizeOverlapping) {
sprayElements = this._getOptimizedRects(sprayElements);
}
var sprayGroup = new fabric.Group(sprayElements);
if (this.shadow) {
sprayGroup.set("shadow", new fabric.Shadow(this.shadow));
}
this.canvas.fire("before:path:created", { path: sprayGroup });
this.canvas.add(sprayGroup);
this.canvas.fire("path:created", { path: sprayGroup });
this.canvas.clearContext(this.canvas.contextTop);
this._resetShadow();
this.canvas.renderOnAddRemove = originalRenderSetting;
this.canvas.requestRenderAll();
};
This example demonstrates the concept, though the random color combination may not be aesthetically pleasing.