Vue + fabric JS to realize simple drawing board

Because the company needs to use the framework of fabric.js, so when learning fabric.js, it has made a demo of such a simple drawing board. Its main functions are: drawing straight lines, drawing circles, drawing rectangles, Draw Bezier curve, detect (i.e. judge whether the mouse moves near the object, if so, adsorb on the object, and I do the detection of Bezier curve, because the idea of line detection is similar to that of Bezier curve), mirror (at present, mirror Bezier curve), delete, adjust the length of the line, display the length of the line, and modify Bezier curve) Curve radian, position and other functions

start

  1. New vue project
  2. Install fabric npm install fabric--save in the project, and import {fabric} from 'fabric' into your. vue folder. The fabric needs to be used in the mounted() life cycle of the. vue file
  3. Write a < canvas id = "main" width = "1920" height = "600" ref = "CVS" >
  • Declaration canvas
let canvas = new fabric.Canvas("main", {
    backgroundColor: "rgb(100,100,200)"
});
Copy code
  • Determine the position of the window and canvas: because the position of the mouse is relative to the entire screen, but the position we need to know is the relative position of the mouse on the canvas, so when the page is initialized, we determine the relative position of the canvas on the screen, subtract the distance from the left on the canvas by the position of the mouse, so as to calculate the relative position of the mouse on the canvas So we need to use offsetX, offsetY
  • Determine if some palette elements are to be disabled or turned on
 canvas.skipTargetFind = true; / / palette element cannot be selected
 canvas.selection = false; / / the palette does not show the selection
 Copy code

This is how the canvas looks after initialization. In order to be clearer, I added a color here

Draw line & & modify line

The drawing of a straight line here is not to give you two fixed points and then display the straight line. We want to draw a straight line just like we are drawing a straight line. Draw a straight line a little bit

  1. Listen for the mouse down event, and store the x,y of this point in two variables (MouseFrom x, MouseFrom y) as the starting point
  2. Listen to the event of raising the mouse. Put the X and y of this point in two variables (mouseToX, mouseToY) as the end point. You may say, that's not to say that two points determine a straight line. How can there be a little effect of drawing the past??? Yes, this will not have the effect we want, so look at the third step
  3. Listen to the event of moving the mouse, and store the X and y of the moving point in two variables (mouseToX, mouseToY) as the end point, we can get the effect we want in this way, but in this way, we will have another problem: the mouse position is moving all the time, and we will draw many lines, just like sending out countless rays from one point. To solve this problem, we can see step 4
  4. In the process of moving, every time you draw the next line, you need to delete the previous line, but this will also lead to the disappearance of the previous line when you draw the next line. When you lift the mouse, you need to draw on the canvas again

==Note: when drawing a line, we need to draw two small spheres on both ends of the line, and the two spheres need to store the information of the line, and the line also needs to store the information of the two spheres, because we need to modify the length and position of the line==

Modify line thinking:

  1. Events monitoring the movement of the ball
  2. Drag the ball to change the coordinates of a section of the line

Main code:

function mouseUpLine(options, canvas) {
    isMouseDown = false;
    mouseToX = options.e.offsetX;
    mouseToY = options.e.offsetY;
    canvas.add(line, point1, point2);
    let lineObj = { 'id': lineArray.length, 'detail': line, 'leftPoint': point1, 'rightPoint': point2 };
    lineArray.push(lineObj);
    return computedLineLength(mouseFromX, mouseFromY, mouseToX, mouseToY);
}

function lineData() {
    return lineArray;
}

function ObjectMove(options, canvas) {
    var p = options.target;
    let lineLength = 0;
    if (p.line1) {
        p.line1.set({ x2: p.left, y2: p.top });
        lineLength = computedLineLength(p.line1.x1, p.line1.y1, p.line1.x2, p.line1.y2);
    }
    if (p.line2) {
        p.line2.set({ x1: p.left, y1: p.top });
        lineLength = computedLineLength(p.line2.x1, p.line2.y1, p.line2.x2, p.line2.y2);
    }
    canvas.renderAll();
    return lineLength;
}
// Draw a straight line
function drawLine(mouseFromX, mouseFromY, mouseToX, mouseToY) {
    line = new fabric.Line([mouseFromX, mouseFromY, mouseToX, mouseToY], {
        fill: 'green',
        stroke: 'green', // stroke color 
        strokeWidth: 2, // Stroke width
        hasControls: false, // Can I zoom in and out when selected
        hasRotatingPoint: false, // Can I rotate when selected
        hasBorders: false, // Whether there is a border when selected
        selectable: false,
        evented: false
    });
    point1 = makeCircle(line.get('x2'), line.get('y2'), line, null);
    point2 = makeCircle(line.get('x1'), line.get('y1'), null, line);
    line.point1 = point1;
    line.point2 = point2;
    return line;
}
// Draw ball
function makeCircle(left, top, line1, line2) {
    var c = new fabric.Circle({
        left: left,
        top: top,
        strokeWidth: 2,
        radius: 6,
        fill: '#fff',
        stroke: '#666',
        originX: 'center',
        originY: 'center'
    });
    c.hasControls = c.hasBorders = false;

    c.line1 = line1;
    c.line2 = line2;

    return c;
}
Copy code

Circle drawing

Main code of drawing circle: (the idea is relatively simple, so I won't talk about it)

function makeCircle(left, top, r) {
  circleObj = new fabric.Circle({
    left: left,
    top: top,
    strokeWidth: 2,
    radius: r,
    fill: '#fff',
    stroke: '#666',
    originX: 'center',
    originY: 'center'
  });
  circleObj.hasControls = circleObj.hasBorders = false;
}
Copy code

Draw rectangle

Draw the main code of rectangle: (the idea is relatively simple, let's not talk about it)

function makeRect(left, top, width, height) {
  rectObj = new fabric.Rect({
    left: left,
    top: top,
    height: height,
    width: width,
    fill: 'white',
    stroke: '#666'
  });
  rectObj.hasControls = rectObj.hasBorders = false;
}
Copy code

Draw bezier curve

Thinking of drawing bezier curve: (it's a bit troublesome to draw this thing, so it's suggested to start with the relevant content of Baidu bezier curve)

  1. We use the bezier curve of the third order to draw the path. Generally, the path is based on 'M start point x, start point y, C 1 control point x, 1 control point y, 2 control point x, 2 control point y, end point x, end point y'. In the illustration, the red ball and the blue ball are the control points, and the white ball are the start and end points, which are collectively referred to as anchor points
  2. In order to keep the connection of the first bezier curve and the second bezier curve smooth, the No.2 control point of the first bezier curve and the No.1 control point of the second bezier curve need to be on a straight line (the anchor point in my treatment is the midpoint of the two control points)
  3. There is a certain relationship between anchor point and control point, so when we create an anchor point, we will create two control points at the same time (not adding straight lines to the canvas), and the control point coincides with the anchor point. The information of these two control points needs to be stored in the anchor point for future use
  4. To draw a continuous line, when drawing the second bezier curve, you need to take the end point of the previous bezier curve as the start point of the second bezier curve
  5. End with space

Ideas for moving anchors and control points:

  1. Click the anchor and draw two control points existing in the anchor
  2. If you want to change the radian of bezier curve, when you need to move the control point (for example, move the blue control point), draw another anchor point (red control point) that does not move according to the anchor point as the end point of two control points, and update the coordinate information of these two control points on this anchor point
  3. If you want to move an anchor, you need to record the x, y of the anchor's movement to figure out how much it has moved, and also increase or decrease the corresponding movement distance of the control point on the anchor
  4. According to the latest coordinate information, draw the bezier curve again

Main code:

// Mouse movement
function bezierMouseMove(options, canvas) {
    if (!anchorArr.length) return;
    let point = { left: options.e.offsetX, top: options.e.offsetY };
    if (!isMouseDown) {
        // isFinish = false;
        canvas.remove(temBezier, temAnchor);
        let anchor = anchorArr[anchorArr.length - 1];
        makeBezier(anchor, anchor.nextConP, anchor.nextConP, point);
        let startCon = makeBezierConP(point.left, point.top, 'red');
        temAnchor = makeBezierAnchor(point.left, point.top, startCon, startCon);
        canvas.add(temBezier, temAnchor);
    } else {
        if (anchorArr.length > 1) {
            canvas.remove(temBezier);
            // Starting point
            let preAnchor = anchorArr[anchorArr.length - 2];
            // End point
            currentAnchor = anchorArr[anchorArr.length - 1];
            // The mouse position is the back control point of the current anchor
            let currentPreContrl = { left: point.left, top: point.top };
            let currentNextContrl = { left: 2 * currentAnchor.left - point.left, top: 2 * currentAnchor.top - point.top };
            // Each drawing is the last point and the last point of the array in the array. The last point is the first point and the last point of bezier
            makeBezier(preAnchor, preAnchor.nextConP, currentAnchor.preConP, currentAnchor);
            canvas.add(temBezier);
            temCanvas = canvas;
            // Update the post control point of the current anchor
            currentAnchor.preConP = currentNextContrl;
            currentAnchor.nextConP = currentPreContrl;
            currentAnchor.preConP.name = 'preAnchor';
            currentAnchor.nextConP.name = 'nextAnchor';
        }
    }
}
// Move control point
function changeControl(options, canvas) {
    console.log(options);
    clickPostion = { 'left': options.transform.original.left, 'top': options.transform.original.top };
    if (!targetAnchor) return;
    let controlPoint = options.target;
    let whichBezier = bezierArray[targetAnchor.lineName];
    // console.log(targetAnchor);
    let point = { 'left': options.e.offsetX, 'top': options.e.offsetY };
    // Determine whether the front control point or the back control point is clicked through the color of the control point
    if (controlPoint.fill === 'red') {
        // Change the coordinates of the front and rear control points
        targetAnchor.preConP.left = point.left;
        targetAnchor.preConP.top = point.top;
        targetAnchor.nextConP.left = targetAnchor.left * 2 - point.left;
        targetAnchor.nextConP.top = targetAnchor.top * 2 - point.top;
        // Redraw control points
        canvas.remove(preContPoint, nextContPoint);
        preContPoint = makeBezierConP(targetAnchor.preConP.left, targetAnchor.preConP.top, 'red');
        nextContPoint = makeBezierConP(targetAnchor.nextConP.left, targetAnchor.nextConP.top, 'blue');
        canvas.add(preContPoint, nextContPoint);
        // console.log(whichBezier.detail[targetAnchor.id]);
    } else if (controlPoint.fill === 'blue') {
        targetAnchor.preConP.left = targetAnchor.left * 2 - point.left;
        targetAnchor.preConP.top = targetAnchor.top * 2 - point.top;
        targetAnchor.nextConP.left = point.left;
        targetAnchor.nextConP.top = point.top;
        canvas.remove(preContPoint, nextContPoint);
        preContPoint = makeBezierConP(targetAnchor.preConP.left, targetAnchor.preConP.top, 'red');
        nextContPoint = makeBezierConP(targetAnchor.nextConP.left, targetAnchor.nextConP.top, 'blue');
        canvas.add(preContPoint, nextContPoint);
    } else if (controlPoint.fill === 'white') {
        console.log(clickPostion);
        let moveLeft = point.left - clickPostion.left;
        let moveTop = point.top - clickPostion.top;
        // console.log(moveTop, moveLeft, targetAnchor.preConP.left);
        targetAnchor.preConP.left = targetAnchor.preConP.left + moveLeft - lastMoveLeft;
        targetAnchor.preConP.top = targetAnchor.preConP.top + moveTop - lastMoveTop;
        targetAnchor.nextConP.left = targetAnchor.nextConP.left + moveLeft - lastMoveLeft;
        targetAnchor.nextConP.top = targetAnchor.nextConP.top + moveTop - lastMoveTop;
        canvas.remove(preContPoint, nextContPoint);
        preContPoint = makeBezierConP(targetAnchor.preConP.left, targetAnchor.preConP.top, 'red');
        nextContPoint = makeBezierConP(targetAnchor.nextConP.left, targetAnchor.nextConP.top, 'blue');
        canvas.add(preContPoint, nextContPoint);
        lastMoveLeft = moveLeft;
        lastMoveTop = moveTop;
    }
    // console.log('changed ', targetAnchor, bezierArray);
    // Update the current anchor information of the current bezier curve
    bezierArray[targetAnchor.lineName].detail[targetAnchor.id] = targetAnchor;
    // For the last point, because there is no next anchor of the currently selected point
    if (whichBezier.detail[targetAnchor.id + 1]) {
        canvas.remove(whichBezier.segmentBezier[targetAnchor.id]);
        // Draw the next bezier curve parameter of the currently selected anchor: the currently selected anchor, the back control point of the currently selected anchor, the front control point of the next anchor of the currently selected anchor, and the next anchor of the currently selected anchor
        newNextBezier = makeBezier(whichBezier.detail[targetAnchor.id], whichBezier.detail[targetAnchor.id].nextConP, whichBezier.detail[targetAnchor.id + 1].preConP, whichBezier.detail[targetAnchor.id + 1]);
        // Update the next bezier curve of the currently selected anchor
        whichBezier.segmentBezier[targetAnchor.id] = newNextBezier;
        canvas.add(whichBezier.segmentBezier[targetAnchor.id]);
    }
    // For the start point because there is no previous anchor for the currently selected point
    if (whichBezier.detail[targetAnchor.id - 1]) {
        canvas.remove(whichBezier.segmentBezier[targetAnchor.id - 1]);
        // Draw the previous bezier curve parameters of the currently selected anchor: the previous anchor point of the currently selected anchor point, the back control point of the previous anchor point of the currently selected anchor point, the front control point of the currently selected anchor point, and the front selected anchor point of the current year
        newPreBezier = makeBezier(whichBezier.detail[targetAnchor.id - 1], whichBezier.detail[targetAnchor.id - 1].nextConP, whichBezier.detail[targetAnchor.id].preConP, whichBezier.detail[targetAnchor.id]);
        // Update the previous bezier curve of the currently selected anchor
        whichBezier.segmentBezier[targetAnchor.id - 1] = newPreBezier;
        canvas.add(whichBezier.segmentBezier[targetAnchor.id - 1]);
    }
}
// Create anchor
function makeBezierAnchor(left, top, preConP, nextConP) {
    var c = new fabric.Circle({
        left: left,
        top: top,
        strokeWidth: 2,
        radius: 6,
        fill: 'white',
        stroke: '#666',
        originX: 'center',
        originY: 'center'
    });

    c.hasBorders = c.hasControls = false;
    // preConP is the control point of the previous line nextConP is the control point of the next line
    c.preConP = preConP;
    c.nextConP = nextConP;
    c.name = 'anchor';
    c.lineName = bezierArray.length;
    c.id = anchorArr.length;
    return c;
}
// Press the space bar to finish drawing
function keyDown(event) {
    if (event && event.keyCode === 32) {
        temCanvas.remove(temAnchor, temBezier, preContPoint, nextContPoint);
        segmentBezierArr.forEach(element => {
            element.belongToId = bezierArray.length;
        });
        bezierArray.push({ id: bezierArray.length, 'detail': anchorArr, 'segmentBezier': segmentBezierArr });
        anchorArr.forEach(item => {
            temCanvas.bringToFront(item);
        });
        temBezier = null;
        temAnchor = null;
        currentAnchor = null;
        preContPoint = null;
        nextContPoint = null;
        isMouseDown = false;
        anchorArr = [];
        segmentBezierArr = [];
        console.log(bezierArray);
        // isFinish = true;
    }
}
Copy code

delete

Idea: fabric provides getActiveObject() to get the selected object, so as long as you get the object, and then canvas.remove (the object) will do

Main code:

canvas.skipTargetFind = false;
  if (canvas.getActiveObject() && canvas.getActiveObject().belongToId === undefined) {
    canvas.remove(canvas.getActiveObject().point1);
    canvas.remove(canvas.getActiveObject().point2);
    canvas.remove(canvas.getActiveObject());
  }
  if (canvas.getActiveObject() && canvas.getActiveObject().belongToId !== undefined) {
    deleteBezier(options, canvas);
  }
Copy code

Detection

Idea: (I only do curve detection here, line detection is similar to curve detection)

  1. We know the equation of the third order bezier curve. According to the equation, we can get 100 or 1000 points on the curve
  2. If the distance between the mouse and the point on the curve is less than the shortest value, then draw a hollow ball with that point as the center of the circle, and a detection effect will appear
  3. But there is also a problem. If there are many points less than the shortest value, many balls will be drawn. So we need to find the closest one to the mouse
/**
     * Bessel curve equation of third order
     * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]
     * @param t  Curve length scale
     * @param p0 starting point
     * @param p1 Control point 1
     * @param p2 Control point 2
     * @param p3 Termination point
     * @return t Corresponding point
     */
    CalculateBezierPointForCubic : function ( t, p0, p1, p2, p3) {
        var point = cc.p( 0, 0 );
        var temp = 1 - t;
        point.x = p0.x * temp * temp * temp + 3 * p1.x * t * temp * temp + 3 * p2.x * t * t * temp + p3.x * t * t * t;
        point.y = p0.y * temp * temp * temp + 3 * p1.y * t * temp * temp + 3 * p2.y * t * t * temp + p3.y * t * t * t;
        return point;
    }
Copy code

Main code:

function mouseMove(options, canvas) {
    let point = { 'x': options.e.offsetX, 'y': options.e.offsetY };
    let min = Infinity;
    linePostionArr.forEach(item => {
        let len = computedMin(point, item);
        if (len < minDetect && min > len) {
            min = len;
            minPoint = item;
        }
    });
    if (!minPoint) return;
    // console.log(minPoint);
    let l = computedMin(point, minPoint);
    if (l < minDetect) {
        canvas.remove(detectPoint);
        detectPoint = makePoint(minPoint.x, minPoint.y);
        canvas.add(detectPoint);
    } else {
        canvas.remove(detectPoint);
    }
}
Copy code

image

Idea: (at present, only the bezier curve is mirrored, and the straight line is similar)

  1. When we drew the beizer curve, we would put all the anchors in an array
  2. Calculate the perpendicularity of the equation between the anchor / control point and the line we draw
  3. Draw a symmetrical anchor / control point with this perpendicular foot as the midpoint
  4. Draw a mirror bezier curve according to the symmetry point

Main code: mirror.js

// Return to midpoint
function intersectionPoint(x1, y1, x2, y2, point) {
    let linek = (y2 - y1) / (x2 - x1);
    let b1 = y1 - linek * x1;
    let verticalk = -1 / linek;
    let b2 = point.top - verticalk * point.left;
    let x = (b2 - b1) / (linek - verticalk);
    let y = (linek * linek * b2 + b1) / (linek * linek + 1);
    return { 'left': x, 'top': y };
}
// Modify the coordinates of the points and store them in a new array
function SymmetricalPoint(mirrorArray) {
    mirrorArray.forEach((item, index) => {
        console.log(index, item);
        let centerPoint = intersectionPoint(mouseFromX, mouseFromY, mouseToX, mouseToY, item);
        // console.log('I am the anchor center ', centerPoint);
        let startPoint = computedSymmetricalPoint(centerPoint.left, centerPoint.top, item.left, item.top);
        item.left = startPoint.left;
        item.top = startPoint.top;
        let centerPointPre = intersectionPoint(mouseFromX, mouseFromY, mouseToX, mouseToY, item.preConP);
        // console.log('I am the center of the front control point ', index, centerPointPre);
        let preControl = computedSymmetricalPoint(centerPointPre.left, centerPointPre.top, item.preConP.left, item.preConP.top);
        // item.preConP.set({ 'left': preControl.left, 'top': preControl.top});
        let newItem = Object.assign({}, item.preConP);
        newItem.left = preControl.left;
        newItem.top = preControl.top;
        item.preConP = newItem;
        // console.log('see if the control point changes', item.preConP);
        let centerPointNext = intersectionPoint(mouseFromX, mouseFromY, mouseToX, mouseToY, item.nextConP);
        // console.log('I am the center of the post control point ', index, centerPointNext);
        let nextControl = computedSymmetricalPoint(centerPointNext.left, centerPointNext.top, item.nextConP.left, item.nextConP.top);
        item.nextConP.left = nextControl.left;
        item.nextConP.top = nextControl.top;
        mirrorPointArr.push(item);
    });
    // console.log('--- view the processed mirrorpointar ------', mirrorpointar);
}
// Calculate symmetry point
function computedSymmetricalPoint(cLeft, cTop, xLeft, xTop) {
    // console.log(cLeft, cTop, xLeft, xTop);
    let left = 2 * cLeft - xLeft;
    let top = 2 * cTop - xTop;
    let point = { 'left': left, 'top': top };
    return point;
}
Copy code

==Note: mirror.js only exists in the array for anchor points, so this JS file can only mirror bezier curve, but if you can store the drawn curve or line path as' m 495 105 C 495 105 707 204 619 302 C 531 400 531 400 516 492 l 200 L 500 500 ' In this type, you can directly use the mirrorPath.js file for mirroring, which can successfully mirror no matter the line curve or other types==

Some common API s

Object:

fabric.Circle circle circle fabric.Ellipse ellipse fabric.Line straight fabric.Polygon fabric.Polyline fabric.Rect rectangle fabric.Triangle

method:

add(object) add insertAt(object,index) add remove(object) remove forEachObject loop traverse getObjects() get all objects item(int) get sub item isEmpty() judge whether empty palette size() number of palette elements contains(object) query whether contain an element fabric.util.cos fabric.util.sin fabric.util.drawDashedLine draw dotted line getWidth() setWidth() getHeight() clear() clear renderAll() redraw requestRenderAll() request to redraw rendercanvas() redraw palette getCenter().top/left get the center coordinate toDatalessJSON() palette information is sequenced into the smallest json toJSON() palette information is sequenced into json moveTo(object,index) move dispose() to release setCursor() to set the gesture icon getSelectionContext() to get the selected context getSelectionElement() to get the selected element getActiveObject() to get the selected object getActiveObjects() to get the selected multiple objects discardActiveObject() to cancel the type of the currently selected object isType() picture set color = canvas. Set ("full", ""); Rotate() set rotation angle setCoords() set coordinates

event:

object:added object:removed object:modified object:rotating object:scaling object:moving object:selected this method v2 has been abandoned, using selection:created instead, multi selection will not trigger before:selection:cleared selection:cleared selection:updated selection:created path:created mouse:down mouse:move mouse:up mouse:over mouse:out mouse:dblclick

Common properties:

canvas.isDrawingMode = true; canvas can be drawn freely. Selectable = false; the control cannot be selected, and it will not be operated. Selection = true; canvas.skipTargetFind = true is selected in the palette display; the entire palette element cannot be selected. canvas.freeDrawingBrush.color = "#E34F51" sets the color of the freeDrawingBrush.width

IText method:

selectAll() select all getSelectedText() get the selected textexitedditioning() exit editing mode

End

If you need to see the source code, you can click A kind of [project github address]:( github.com/JZHEY/Draw-... )If there is any problem in the above content, please correct it

Tags: Vue less JSON github

Posted on Sat, 09 May 2020 01:48:14 -0400 by samohtwerdna