What is the fabric
Fabric is a powerful JavaScript library that runs on the HTML5 canvas. Fabric provides an interactive object model for Canvas, as well as an SVG-to-Canvas parser.
The difference with Canvas
To illustrate the difference between fabric and canvas as a simple example, suppose we want to draw a red rectangle on a canvas:
<canvas id="c"></canvas>
Copy the code
// The native Canvas API
// There is a canvas element with id c
var canvasEl = document.getElementById('c');
// Get the 2D bitmap model
var ctx = canvasEl.getContext('2d');
// Set the fill color
ctx.fillStyle = 'red';
// create a rectangle with coordinates 100,190 and dimensions 20,20
ctx.fillRect(100.100.20.20);
Copy the code
// Use fabric implementation
Create a Fabric instance with the native Canvas element
var canvas = new fabric.Canvas('c');
// Create a rectangle object
var rect = new fabric.Rect({
left: 100.top: 100.fill: 'red'.width: 20.height: 20
});
// Add the rectangle to the canvas canvas
canvas.add(rect);
Copy the code
Make a sketchpad using Fabric
Because space is limited, we will not cover the entire API of Fabric in detail here
The fabric’s official website
Learning, drawing board complete implementation can refer to
The fabric – the drawing – board warehouse
Artboard function decomposition
An artboard contains the following functions
- The brush
- line
- rectangular
- circular
- The text
- The eraser
- mobile
- The zoom
- undo
- reduction
- empty
- export
Drawing board function realization
The following are classified according to how fabric implements different functions
Initialize the
this.canvas = new fabric.Canvas('canvasId')
Copy the code
The brush
Fabric encapsulates the brush function and allows us to use it with some configuration
// Open the canvas free painting mode
this.canvas.isDrawingMode = true;
// Set free Paint to pencil
this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas);
// Set the brush color and brush line size in free painting mode
this. Canvas. FreeDrawingBrush. Color = "#000000";this.canvas.freeDrawingBrush.width = 4;
Copy the code
The eraser
The usage of eraser and brush is basically the same, the user uses the mouse to freely draw or erase, so the API of freeDrawingBrush is used. Note that the fabric.js base library does not provide the eraser module, so we need to introduce eraser_brush.mixin.js as an additional file.
Import {fabric} from 'fabric' // Eraser_brush_mixin.js import '@/libs/eraser_brush.mixin.js'... / / to enable free painting pattern this. Canvas. IsDrawingMode = true / / free drawing mode Brush type is set to the eraser object. This canvas. FreeDrawingBrush = new Fabric. EraserBrush (enclosing canvas) / / set the eraser size enclosing canvas. FreeDrawingBrush. Width = 4Copy the code
Development of the eraser function encountered the pit:
// Note that after introducing eraser_brush.mixin.js,
/ / modify the canvas background color if you use this. Canvas. SetBackgroundColor (' # XXXXXX) complains
// The correct way to change the background color of the canvas is:
this.canvas.setBackgroundColor('#xxxxxx'.undefined, { erasable: false })
Copy the code
Lines, rectangles, circles
To draw these three shapes on the canvas with the mouse, we need to listen for two mouse events on the canvas
- Listen for mouse-down events: The current coordinates are set to the starting coordinates
- Monitor mouse movement events: record the current coordinates in real time and set the current coordinates as the endpoint coordinates, and draw graphs dynamically according to the starting and endpoint coordinates
This.canvas. On ("mouse:down", (options) => { X = options.e.clientx - this.canvas._offset. Left; this.mouseFrom.y = options.e.clientY - this.canvas._offset.top; }); This.canvas. On ("mouse:move", (options) => { X = options.e.clientx - this.canvas._offset. Left this.mouseto. y = options.e.clienty - this.canvas._offset.top switch (current_action) { case: 'line': this.drawLine(); break; case: 'rect': this.drawRect(); break; case: 'circle': this.drawCircle(); break; }});Copy the code
// Draw a line
drawLine() {
// Create a line object based on the saved mouse starting point coordinates
let canvasObject = new fabric.Line(
[
this.getTransformedPosX(this.mouseFrom.x),
this.getTransformedPosY(this.mouseFrom.y),
this.getTransformedPosX(this.mouseTo.x),
this.getTransformedPosY(this.mouseTo.y),
],
{
fill: this.fillColor,
stroke: this.strokeColor,
strokeWidth: this.lineSize,
}
);
this.canvas.add(canvasObject)
},
Copy the code
// Draw a rectangle
drawRect() {
// Calculate the length and width of the rectangle
let left = this.getTransformedPosX(this.mouseFrom.x);
let top = this.getTransformedPosY(this.mouseFrom.y);
let width = this.mouseTo.x - this.mouseFrom.x;
let height = this.mouseTo.y - this.mouseFrom.y;
// Create a rectangle object
let canvasObject = new fabric.Rect({
left: left,
top: top,
width: width,
height: height,
stroke: this.strokeColor,
fill: this.fillColor,
strokeWidth: this.lineSize,
});
// Draw a rectangle
this.canvas.add(canvasObject)
},
Copy the code
// Draw a circle
drawCircle() {
let left = this.getTransformedPosX(this.mouseFrom.x);
let top = this.getTransformedPosY(this.mouseFrom.y);
// Calculate the radius of the circle
let radius =
Math.sqrt(
(this.getTransformedPosX(this.mouseTo.x) - left) *
(this.getTransformedPosY(this.mouseTo.x) - left) +
(this.getTransformedPosX(this.mouseTo.y) - top) *
(this.getTransformedPosY(this.mouseTo.y) - top)
) / 2;
// Create a prototype object
let canvasObject = new fabric.Circle({
left: left,
top: top,
stroke: this.strokeColor,
fill: this.fillColor,
radius: radius,
strokeWidth: this.lineSize,
});
this.canvas.add(canvasObject)
}
Copy the code
// Since the canvas will be moved or scaled, the mouse coordinates on the canvas need to be handled accordingly to be the available coordinates relative to the canvas
getTransformedPosX(x) {
let zoom = Number(this.canvas.getZoom())
return (x - this.canvas.viewportTransform[4]) / zoom;
},
getTransformedPosY(y) {
let zoom = Number(this.canvas.getZoom())
return (y - this.canvas.viewportTransform[5]) / zoom;
},
Copy the code
The text
The process for drawing text is as follows:
- First mouse down: record the coordinates of the current text and make the text editable
- Second mouse press: Makes the text object uneditable
drawText(){
if (!this.textObject) {
// There is no text object currently being drawn, the mouse is pressed for the first time
// According to the mouse-pressed starting point coordinate text object
this.textObject = new fabric.Textbox("", {
left: this.getTransformedPosX(this.mouseFrom.x),
top: this.getTransformedPosY(this.mouseFrom.y),
fontSize: this.fontSize,
fill: this.strokeColor,
hasControls: false.editable: true.width: 30.backgroundColor: "#fff".selectable: false});this.canvas.add(this.textObject);
// Open edit mode for text
this.textObject.enterEditing();
// Text edit box gets focus
this.textObject.hiddenTextarea.focus();
} else {
// Press the second mouse button to remove the current text object from edit mode
this.textObject.exitEditing();
this.textObject.set("backgroundColor"."Rgba (0,0,0,0)");
if (this.textObject.text == "") {
this.canvas.remove(this.textObject);
}
this.canvas.renderAll();
this.textObject = null;
return; }}Copy the code
mobile
The function of movement is not to move the canvas directly, because the canvas extends infinitely in four directions: up, down, left and right. The part we can see at present can be understood as a viewport, and the canvas width and height defined by us is only the width and height of the viewport defined. The position of the current canvas display is determined by defining four coordinate points of the viewport. Moving the canvas can be understood as moving the viewport, that is, modifying the coordinate position of the upper left corner of the viewport. When the viewport is moved, the area displayed by the canvas naturally changes.
move() {
// Gets the current canvas viewport move object
var vpt = this.canvas.viewportTransform;
// Modify the coordinates of the upper left corner of the viewport by calculating the distance of the mouse
vpt[4] + =this.mouseTo.x - this.mouseFrom.x;
vpt[5] + =this.mouseTo.y - this.mouseFrom.y;
// Re-render the canvas after the viewport coordinates are modified
this.canvas.requestRenderAll();
},
Copy the code
The zoom
zoom(flag){
let zoom = this.canvas.getZoom();
if (flag > 0) {
Enlarge / /
zoom *= 1.1;
} else {
/ / to narrow
zoom *= 0.9;
}
// zoom cannot be larger than 20 or smaller than 0.01
zoom = zoom > 20 ? 20 : zoom;
zoom = zoom < 0.01 ? 0.01 : zoom;
this.canvas.setZoom(zoom);
}
Copy the code
Undo, undo
We need to maintain an array of the current state of the painting by the user at the end of each operation, and an index pointing to a location in the array that represents the current state of the canvas. Undo and restore functions can be implemented by index forward and backward
This.canvas. on("after:render", () => {// Adding graphics to the canvas or using an eraser will trigger the after:render event.
// This callback is triggered frequently during painting, so the current state is recorded at 1s intervals
if (this.recordTimer) {
clearTimeout(this.recordTimer)
this.recordTimer = null
}
this.recordTimer = setTimeout(() = > {
this.stateArr.push(JSON.stringify(this.canvas))
this.stateIdx++
}, 1000)})// Undo or restore
tapHistoryBtn(flag) {
let stateIdx = this.stateIdx + flag
// Determine if you have reached the first step
if (stateIdx < 0) return;
// Determine if you have reached the last step
if (stateIdx >= this.stateArr.length) return;
if (this.stateArr[stateIdx]) {
this.canvas.loadFromJSON(this.stateArr[stateIdx])
if (this.canvas.getObjects().length > 0) {
this.canvas.getObjects().forEach(item= > {
item.set('selectable'.false)})}this.stateIdx = stateIdx
}
}
Copy the code
empty
Clearing is the removal of all the graphics in the canvas and is very simple to implement
clearAll(){
// Get all objects in the canvas
let children = this.canvas.getObjects()
if (children.length > 0) {
// Remove all objects
this.canvas.remove(... children) } }Copy the code
export
Canvas is generally exported as Base64 data. It is very simple to export Canvas base64 using fabric framework, but there are two pits that need to be noted:
- By default, fabric exports the content of the current viewport of the canvas. If the canvas is moved or enlarged, the content outside the viewport is not included.
- After the canvas is moved, the background color of the area outside the original viewport is transparent. If the exported content is exported in PNG format, the non-initial viewport area is transparent; if the exported content is not exported in PNG format, the non-initial viewport area is black.
export() {
// Because we need to add background color to the part outside the initial viewport area, we do this by cloning the canvas without modifying the original canvas
this.canvas.clone(cvs= > {
// Go through all pairs of objects, get the minimum and maximum coordinates, calculate the upper left corner coordinates of the exported content and the width and height of the exported content according to the minimum and maximum coordinates of the graph in the canvas
let top = 0
let left = 0
let width = this.canvas.width
let height = this.canvas.height
var objects = cvs.getObjects();
if(objects.length > 0) {var rect = objects[0].getBoundingRect();
var minX = rect.left;
var minY = rect.top;
var maxX = rect.left + rect.width;
var maxY = rect.top + rect.height;
for(var i = 1; i<objects.length; i++){
rect = objects[i].getBoundingRect();
minX = Math.min(minX, rect.left);
minY= Math.min(minY, rect.top);
maxX = Math.max(maxX, rect.left + rect.width);
maxY= Math.max(maxY, rect.top + rect.height);
}
// Add 100px space up, down, left, and right, so that the exported image does not fit the edge of the image but has a gap.
top = minY - 100
left = minX - 100
width = maxX - minX + 200
height = maxY - minY + 200
// Add a rectangle with the same color as the background color to the current canvas to solve the problem of making the background color outside the initial viewport transparent.
cvs.sendToBack(new fabric.Rect({
left,
top,
width,
height,
stroke: 'rgba (0,0,0,0)'.fill: this.bgColor,
strokeWidth: 0}}))// Export the canvas as base64 data
const dataURL = cvs.toDataURL({
format: 'png'.multiplier: cvs.getZoom(),
left,
top,
width,
height
});
return dataURL
})
}
Copy the code