一、非流畅实现
1.canvas.component.html
<button (click)="switchMark(1)">画笔</button> <button (click)="switchMark(2)">矩形</button> <button (click)="switchMark(3)">圆形</button> <button (click)="switchMark(4)">文字</button> <button (click)="switchMark(0)">撤销</button> <input type="text" [(ngModel)]="text"> <canvas class="vp-canvas" #canvas1 width="470" height="300" style="border: 1px solid #ccc;">您的浏览器不支持画布!</canvas>
2.canvas.component.ts
// canvas @ViewChild('canvas1') public canvasRef: ElementRef; // canvas1 public canvas1; // contex1 public context1; // 操作次数 public canvasIndex = -1; // canvas图片 public canvasData = []; // 轨迹结束判断 public isSameMove = false; // 起点对象 public startXY = { x1: 0, y1: 0, x2: 0, y2: 0 }; // 开关 public switchType = null; // 文字 public Text = null; // 初始化canvas public initCanvas() { this.canvas1 = null; this.context1 = null; this.canvasIndex = -1; this.canvasData = []; this.isSameMove = false; this.startXY = { x1: 0, y1: 0, x2: 0, y2: 0 }; this.switchType = null; this.Text = null; } // 生成canvas内容及事件 public createCanvas(base64PImg) { this.initCanvas(); this.canvas1 = this.canvasRef.nativeElement; const context1 = this.canvasRef.nativeElement.getContext('2d'); var img = new Image(); img.onload = function () { context1.drawImage(img, 0, 0, 470, 300); } img.src = base64PImg; this.context1 = context1; // 添加默认图片到历史记录 this.canvasIndex++; this.canvasData.push(base64PImg); // 绑定canvas参数 this.canvas1.canvas1 = this.canvasRef.nativeElement; this.canvas1.context1 = this.canvasRef.nativeElement.getContext('2d'); this.canvas1.canvasIndex = this.canvasIndex; this.canvas1.canvasData = this.canvasData; this.canvas1.startXY = this.startXY; this.canvas1.switchType = this.switchType; this.canvas1.Text = this.Text; // 绑定canvas方法 this.canvas1.windowToCanvas = this.windowToCanvas; // 绑定canvas事件 this.canvas1.onmousedown = this.canvasMouseDown; this.canvas1.onmouseup = this.canvasMouseUp; this.canvas1.onmousemove = this.canvasMouseMove; } // 增加文字内容 public updateText(ev) { this.canvas1.Text = ev; } // 切换绘制 public switchMark(type) { if (type === 0) { this.undo(); } if (type === 0) { this.switchType = null; this.canvas1.switchType = this.switchType; return; } this.switchType = type; this.canvas1.switchType = this.switchType; } // 撤销 public undo() { if (this.canvas1.canvasIndex > 0) { this.canvasData.pop(); this.canvas1.canvasIndex--; this.context1.clearRect(0, 0, 470, 300); var context1 = this.canvasRef.nativeElement.getContext('2d'); var img = new Image(); img.onload = function () { context1.drawImage(img, 0, 0, 470, 300); } img.src = this.canvasData[this.canvas1.canvasIndex]; } else { console.log('不能再继续撤销了'); } } // 求鼠标坐标函数 public windowToCanvas(canvas, x, y) { var rect = canvas.getBoundingClientRect(); return { x: x - rect.left * (canvas.width / rect.width), y: y - rect.top * (canvas.height / rect.height) }; } // 鼠标按下开始绘制图形函数 public canvasMouseDown(e) { if (this.switchType === null) { return; } this.context1.strokeStyle = 'red'; this.context1.fillStyle = 'red'; // 绘制曲线 if (this.switchType === 1) { this.isSameMove = true; var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.context1.beginPath(); this.context1.moveTo(ele.x, ele.y); } // 绘制矩形 if (this.switchType === 2) { var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.startXY.x1 = ele.x; this.startXY.y1 = ele.y; } // 绘制圆形 if (this.switchType === 3) { var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.startXY.x1 = ele.x; this.startXY.y1 = ele.y; } // 绘制文字 if (this.switchType === 4) { var str = this.Text; var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.startXY.x1 = ele.x; this.startXY.y1 = ele.y; this.context1.font = "24px Arial"; this.context1.fillText(str, ele.x, ele.y); } } // 鼠标弹起 public canvasMouseUp(e) { if (this.switchType === null) { return; } // 曲线 if (this.switchType === 1) { this.isSameMove = false; } // 矩形 if (this.switchType === 2) { var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.startXY.x2 = ele.x; this.startXY.y2 = ele.y; this.context1.strokeRect(this.startXY.x1, this.startXY.y1, this.startXY.x2 - this.startXY.x1, this.startXY.y2 - this.startXY.y1); } // 圆形 if (this.switchType === 3) { var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.startXY.x2 = ele.x; this.startXY.y2 = ele.y; this.context1.beginPath(); var cx = (this.startXY.x1 + this.startXY.x2) / 2; var cy = (this.startXY.y1 + this.startXY.y2) / 2; var rx = Math.abs((this.startXY.x1 - this.startXY.x2) / 2); var ry = Math.abs((this.startXY.y1 - this.startXY.y2) / 2); this.context1.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); this.context1.stroke(); } this.canvas1.canvasIndex++; this.canvasData.push(this.canvas1.toDataURL()); } // 鼠标移动 public canvasMouseMove(e) { if (this.switchType === 1) { if (this.isSameMove) { var ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); this.context1.lineTo(ele.x, ele.y); this.context1.stroke(); this.context1.save(); } } }
二、流畅实现
1.canvas.component.html
<canvas class="vp-canvas1" #canvas1 [width]="canvasWidth" [height]="canvasHeight">您的浏览器不支持画布!</canvas> <canvas class="vp-canvas2" #canvas2 [width]="canvasWidth" [height]="canvasHeight">您的浏览器不支持画布!</canvas>
2.canvas.component.ts
/** canvas1 */ @ViewChild('canvas1') public canvas1Ref: ElementRef; /** canvas2 */ @ViewChild('canvas2') public canvas2Ref: ElementRef; /** 绘制类型 */ public readonly markCfg = [ { value: 2, label: '矩形', class: 'mark-rectangle' }, { value: 3, label: '圆形', class: 'mark-ellipse' }, { value: 1, label: '曲线', class: 'mark-curves' }, { value: 4, label: '文字', class: 'mark-text' }, { value: 0, label: '撤销', class: 'rotation-anticw-d2' }, ]; /** 当前绘制类型 */ public switchMarkType: number; /** 鼠标按下状态 */ public isMouseDown: boolean; /** 文字绘制 */ public MarkTexts: string; /** 文字绘制弹窗 */ public nzmdMarkText: boolean; /** 绘制颜色 */ public readonly markColorCfg = ['#ef4343', '#ef7c43']; /** 默认绘制颜色 */ public defaultMarkColor: string; /** canvas1 */ public canvas1; /** canvas1的contex1 */ public context1; /** canvas2 */ public canvas2; /** canvas2的contex2 */ public context2; /** canvas宽度 */ public canvasWidth: number = 952; /** canvas高度 */ public canvasHeight: number = 477; /** canvas操作次数 */ public canvasIndex: number; /** canvas图像源 */ public canvasData; /** 鼠标坐标 */ public mouseXY = { x1: 0, y1: 0, x2: 0, y2: 0 }; /** 初始化canvas */ public initCanvas() { this.switchMarkType = null; this.isMouseDown = false; this.MarkTexts = null; this.nzmdMarkText = false; this.defaultMarkColor = '#ef4343'; this.canvasIndex = -1; this.canvasData = []; this.mouseXY = { x1: 0, y1: 0, x2: 0, y2: 0 }; } /** 生成canvas图像及绑定参数和事件 */ public createCanvas(base64PImg) { this.initCanvas(); this.canvas1 = this.canvas1Ref.nativeElement; this.context1 = this.canvas1Ref.nativeElement.getContext('2d'); this.canvas2 = this.canvas2Ref.nativeElement; this.context2 = this.canvas2Ref.nativeElement.getContext('2d'); // 绑定canvas变量 this.canvas1.canvasIndex = this.canvasIndex; this.canvas1.canvasData = this.canvasData; // 绑定canvas事件 this.canvas1.onmousedown = this.canvasMouseDown; this.canvas1.onmouseup = this.canvasMouseUp; this.canvas1.onmousemove = this.canvasMouseMove; // 添加默认图片到历史记录 this.setCanvasImage(this.context2, this.canvas2, base64PImg, this.canvasWidth, this.canvasHeight, (url) => { this.canvas1.canvasIndex++; this.canvasData.push(url); }); } /** 切换绘制 */ public switchMark(markType: number) { if (markType === 0) { this.undo(); } if (markType !== 4) { this.nzmdMarkText = false; } if (this.switchMarkType === markType || markType === 0) { this.switchMarkType = null; return; } this.switchMarkType = markType; } /** 提交文字绘制 */ public MarkText() { this.context1.font = "24px Arial"; if(isNull(this.MarkTexts)) return console.log('文字内容不能为空'); this.context1.fillText(this.MarkTexts, this.mouseXY.x1, this.mouseXY.y1); this.setCanvasImage(this.context2, this.canvas2, this.canvas1.toDataURL(), this.canvasWidth, this.canvasHeight, (url) => { this.context1.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.canvas1.canvasIndex++; this.canvasData.push(url); }) this.MarkTextCancel(); } /** 取消文字绘制 */ public MarkTextCancel() { this.MarkTexts = null; this.nzmdMarkText = false; } /** 撤销 */ public undo() { if (this.canvas1.canvasIndex > 0) { this.canvasData.pop(); this.canvas1.canvasIndex--; this.setCanvasImage(this.context2, this.canvas2, this.canvasData[this.canvas1.canvasIndex], this.canvasWidth, this.canvasHeight, (url) => { return; }); } else { this.nzmsgSrv.warning('不能再继续撤销了'); } } /** 设置绘制颜色 */ public colorChange(color: string) { this.defaultMarkColor = color; } /** 鼠标按下 */ public canvasMouseDown = (e) => { if (this.switchMarkType === null) { return; } this.isMouseDown = true; // 设置线条样式 this.context1.lineWidth = '2'; this.context1.strokeStyle = this.defaultMarkColor; this.context1.fillStyle = this.defaultMarkColor; // 设置起点坐标 this.setMouseXY(true, e); // 绘制曲线 if (this.switchMarkType === 1) { this.context1.beginPath(); this.context1.moveTo(this.mouseXY.x1, this.mouseXY.y1); } // 设置文字坐标 if (this.switchMarkType === 4) { this.nzmdMarkText = true; } } /** 鼠标弹起 */ public canvasMouseUp = (e) => { this.isMouseDown = false; if (this.switchMarkType === null || this.switchMarkType === 4) { return; } this.setCanvasImage(this.context2, this.canvas2, this.canvas1.toDataURL(), this.canvasWidth, this.canvasHeight, (url) => { this.context1.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.canvas1.canvasIndex++; this.canvasData.push(url); }) } /** 鼠标移动 */ public canvasMouseMove = (e) => { if (this.isMouseDown) { // 清除canvas1上的图像 this.context1.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // 绘制线 if (this.switchMarkType === 1) { // 记录起点坐标 this.setMouseXY(true, e); this.context1.lineTo(this.mouseXY.x1, this.mouseXY.y1); this.context1.stroke(); this.context1.save(); } // 记录终点坐标 this.setMouseXY(false, e); // 绘制矩形 if (this.switchMarkType === 2) { this.context1.strokeRect(this.mouseXY.x1, this.mouseXY.y1, this.mouseXY.x2 - this.mouseXY.x1, this.mouseXY.y2 - this.mouseXY.y1); } // 绘制圆形 if (this.switchMarkType === 3) { this.context1.beginPath(); var cx = (this.mouseXY.x1 + this.mouseXY.x2) / 2; var cy = (this.mouseXY.y1 + this.mouseXY.y2) / 2; var rx = Math.abs((this.mouseXY.x1 - this.mouseXY.x2) / 2); var ry = Math.abs((this.mouseXY.y1 - this.mouseXY.y2) / 2); this.context1.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); this.context1.stroke(); } // 在canvas1上绘制图像 this.context1.drawImage(this.canvas1, 0, 0, this.canvasWidth, this.canvasHeight); } } /** 根据宽高生成canvas图像 */ public setCanvasImage(context, canvas, base64PImg, canvasWidth: number, canvasHeight: number, callback) { let img = new Image(); img.onload = function () { context.drawImage(img, 0, 0, canvasWidth, canvasHeight); callback(canvas.toDataURL()); } img.src = base64PImg; } /** 求鼠标坐标函数 */ public windowToCanvas(canvas, x, y) { const rect = canvas.getBoundingClientRect(); return { x: x - rect.left * (canvas.width / rect.width), y: y - rect.top * (canvas.height / rect.height) }; } /** 记录坐标 */ public setMouseXY(start: boolean, e) { const ele = this.windowToCanvas(this.canvas1, e.clientX, e.clientY); if (start) { this.mouseXY.x1 = ele.x; this.mouseXY.y1 = ele.y; } else { this.mouseXY.x2 = ele.x; this.mouseXY.y2 = ele.y; } }