由於公司須要用fabric.js這個框架,因此在學習fabric.js的時候作了這樣的一個簡易畫圖板的demo,主要功能有:畫直線,畫圓, 畫矩形, 畫貝塞爾曲線,偵測(就是判斷鼠標是否是移動到了這個對象附近,若是是的話,吸附在對象上,我就作了貝塞爾曲線的偵測,由於直線偵測的思路與貝塞爾曲線差很少),鏡像(目前就作了貝塞爾曲線的鏡像),刪除,調整直線長短,顯示直線長度,修改貝塞爾曲線的弧度,位置等功能vue
npm install fabric--save
,將其引入到你的.vue文件夾中 import { fabric } from 'fabric'
,fabric 須要在.vue文件的 mounted()生命週期中使用<canvas id="main" width="1920" height="600" ref="cvs"></canvas>
,而後在mounted中初始化畫布,初始化分爲如下步驟let canvas = new fabric.Canvas("main", {
backgroundColor: "rgb(100,100,200)"
});
複製代碼
canvas.skipTargetFind = true; //畫板元素不能被選中
canvas.selection = false; //畫板不顯示選中
複製代碼
==注意:咱們在劃線的時候須要在線的兩端畫上兩個小圓球,而且這兩個圓球須要存這條直線的信息,直線也要存這兩個圓球的信息,由於咱們到時候要修改直線長度位置之類的==git
修改直線思路:github
主要代碼:npm
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;
}
// 畫直線
function drawLine(mouseFromX, mouseFromY, mouseToX, mouseToY) {
line = new fabric.Line([mouseFromX, mouseFromY, mouseToX, mouseToY], {
fill: 'green',
stroke: 'green', // 筆觸顏色
strokeWidth: 2, // 筆觸寬度
hasControls: false, // 選中時是否能夠放大縮小
hasRotatingPoint: false, // 選中時是否能夠旋轉
hasBorders: false, // 選中時是否有邊框
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;
}
// 畫球
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;
}
複製代碼
畫圓主要代碼:(思路比較簡單,就不講了) json
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;
}
複製代碼
畫矩形主要代碼:(思路比較簡單,就不講了) canvas
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;
}
複製代碼
畫bezier曲線思路:(畫這個東西有點麻煩,建議先百度bezier曲線的相關內容) 數組
移動錨點和控制點的思路:框架
主要代碼:less
// 鼠標移動
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);
// 開始點
let preAnchor = anchorArr[anchorArr.length - 2];
// 結束點
currentAnchor = anchorArr[anchorArr.length - 1];
// 鼠標位置爲當前錨點的後控制點
let currentPreContrl = { left: point.left, top: point.top };
let currentNextContrl = { left: 2 * currentAnchor.left - point.left, top: 2 * currentAnchor.top - point.top };
// 每次畫都是數組中的數組的最後一個點和倒數第二個點爲bezier的第一個點個最後一個點
makeBezier(preAnchor, preAnchor.nextConP, currentAnchor.preConP, currentAnchor);
canvas.add(temBezier);
temCanvas = canvas;
// 更新當前錨點的後控制點
currentAnchor.preConP = currentNextContrl;
currentAnchor.nextConP = currentPreContrl;
currentAnchor.preConP.name = 'preAnchor';
currentAnchor.nextConP.name = 'nextAnchor';
}
}
}
// 移動控制點
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 };
// 經過控制點的顏色,肯定點擊的是前控制點仍是後控制點
if (controlPoint.fill === 'red') {
// 改變先後控制點的座標
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;
// 從新繪製控制點
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('改變過', targetAnchor, bezierArray);
// 更新當前條bezier曲線的當前錨點信息
bezierArray[targetAnchor.lineName].detail[targetAnchor.id] = targetAnchor;
// 針對於最後一個點, 由於沒有當前選中點的後一個錨點
if (whichBezier.detail[targetAnchor.id + 1]) {
canvas.remove(whichBezier.segmentBezier[targetAnchor.id]);
// 畫當前選中錨點的後一條bezier曲線 參數:當前選中的錨點,當前點選中錨點的後控制點, 當前選中錨點的後一個錨點的前控制點,當前選中錨點的後一個錨點
newNextBezier = makeBezier(whichBezier.detail[targetAnchor.id], whichBezier.detail[targetAnchor.id].nextConP, whichBezier.detail[targetAnchor.id + 1].preConP, whichBezier.detail[targetAnchor.id + 1]);
// 更新當前選中錨點的後一條bezier曲線
whichBezier.segmentBezier[targetAnchor.id] = newNextBezier;
canvas.add(whichBezier.segmentBezier[targetAnchor.id]);
}
// 針對於開始點, 由於沒有當前選中點的前一個錨點
if (whichBezier.detail[targetAnchor.id - 1]) {
canvas.remove(whichBezier.segmentBezier[targetAnchor.id - 1]);
// 畫當前選中錨點的前一條bezier曲線 參數:當前選中錨點的前一個錨點, 當前選中錨點的前一個錨點的後控制點,當前選中錨點的前控制點, 當年前選中的錨點
newPreBezier = makeBezier(whichBezier.detail[targetAnchor.id - 1], whichBezier.detail[targetAnchor.id - 1].nextConP, whichBezier.detail[targetAnchor.id].preConP, whichBezier.detail[targetAnchor.id]);
// 更新當前選中錨點的前一條bezier曲線
whichBezier.segmentBezier[targetAnchor.id - 1] = newPreBezier;
canvas.add(whichBezier.segmentBezier[targetAnchor.id - 1]);
}
}
// 建立錨點
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是上一條線的控制點nextConP是下一條線的控制點
c.preConP = preConP;
c.nextConP = nextConP;
c.name = 'anchor';
c.lineName = bezierArray.length;
c.id = anchorArr.length;
return c;
}
// 按空格鍵結束畫圖
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;
}
}
複製代碼
主要代碼:學習
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);
}
複製代碼
/** * 三階貝塞爾曲線方程 * 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 曲線長度比例 * @param p0 起始點 * @param p1 控制點1 * @param p2 控制點2 * @param p3 終止點 * @return t對應的點 */
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;
}
複製代碼
主要代碼:
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);
}
}
複製代碼
主要代碼:mirror.js
// 返回中點位置
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 };
}
// 修改點的座標並存入新的數組
function SymmetricalPoint(mirrorArray) {
mirrorArray.forEach((item, index) => {
console.log(index, item);
let centerPoint = intersectionPoint(mouseFromX, mouseFromY, mouseToX, mouseToY, item);
// console.log('我是錨點中心點', 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('我是前控制點中心點', 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('看下控制點是否改變', item.preConP);
let centerPointNext = intersectionPoint(mouseFromX, mouseFromY, mouseToX, mouseToY, item.nextConP);
// console.log('我是後控制點中心點',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('---查看加工後的mirrorPointArr------', mirrorPointArr);
}
// 計算對稱點
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;
}
複製代碼
==注意:mirror.js是隻針對錨點存在數組中這種儲存方式,因此這個js文件只能鏡像bezier曲線,可是若是你能將畫出來的曲線或者直線路徑存儲爲 'M 495 105 C 495 105 707 204 619 302 C 531 400 531 400 516 492 L 200 200 L 500 500' 這種類型,能夠直接用mirrorPath.js文件進行鏡像,這個能夠將無論直線曲線或者其餘類型都能成功鏡像==
對象:
fabric.Circle 圓 fabric.Ellipse 橢圓 fabric.Line 直線 fabric.Polygon fabric.Polyline fabric.Rect 矩形 fabric.Triangle 三角形
方法:
add(object) 添加 insertAt(object,index) 添加 remove(object) 移除 forEachObject 循環遍歷 getObjects() 獲取全部對象 item(int) 獲取子項 isEmpty() 判斷是否空畫板 size() 畫板元素個數 contains(object) 查詢是否包含某個元素 fabric.util.cos fabric.util.sin fabric.util.drawDashedLine 繪製虛線 getWidth() setWidth() getHeight() clear() 清空 renderAll() 重繪 requestRenderAll() 請求從新渲染 rendercanvas() 重繪畫板 getCenter().top/left 獲取中心座標 toDatalessJSON() 畫板信息序列化成最小的json toJSON() 畫板信息序列化成json moveTo(object,index) 移動 dispose() 釋放 setCursor() 設置手勢圖標 getSelectionContext()獲取選中的context getSelectionElement()獲取選中的元素 getActiveObject() 獲取選中的對象 getActiveObjects() 獲取選中的多個對象 discardActiveObject()取消當前選中對象 isType() 圖片的類型 setColor(color) = canvas.set("full",""); rotate() 設置旋轉角度 setCoords() 設置座標
事件:
object:added object:removed object:modified object:rotating object:scaling object:moving object:selected 這個方法v2已經廢棄,使用selection:created替代,多選不會觸發 before:selection:cleared selection:cleared selection:updated selection:created path:created mouse:down mouse:move mouse:up mouse:over mouse:out mouse:dblclick
經常使用屬性:
canvas.isDrawingMode = true; 能夠自由繪製 canvas.selectable = false; 控件不能被選擇,不會被操做 canvas.selection = true; 畫板顯示選中 canvas.skipTargetFind = true; 整個畫板元素不能被選中 canvas.freeDrawingBrush.color = "#E34F51" 設置自由繪畫筆的顏色 freeDrawingBrush.width 自由繪筆觸寬度
IText的方法:
selectAll() 選擇所有 getSelectedText() 獲取選中的文本 exitEditing() 退出編輯模式
若是須要看源碼的話,能夠點擊👉[項目github地址]:(github.com/JZHEY/Draw-…) 上面內容若是寫的有什麼問題的話,歡迎你們指正🤞🤞🤞🤞