由于公司须要用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-…) 上面内容若是写的有什么问题的话,欢迎你们指正🤞🤞🤞🤞