vue+canvas实现简易画板
需求:
默认后台返回的数据渲染到画布上,然后用户可以编辑重新画线,并且可以点击要移除的线条进行移除。
现在做的交互是选中需要移除的线条高亮显示,然后双击进行移除。
<div id="app"> <canvas id="myCanvas" width="600px" height="380px" class="canvas" @mousedown="drawLineMousedown($event)" @mousemove="drawLineMousemove($event)" @mouseup="drawFinish()" @click="select($event)" @dblclick="deleteLine($event)"> </canvas> <input type="button" class="pencil" value="铅笔" @click="addClick($event)"> <input type="button" class="clearBoard" value="清屏" @click="clearBoard()"> <input type="button" class="delete" value="橡皮擦" @click="addClick($event)"> </div>
new Vue({ el: "#app", data: { baseline: [ { idx: 0, x: 10, y: 300, }, { idx: 1, x: 310, y: 300, } ], contour: [ { "idx": 0, "x": 10, "y": 300 }, { "idx": 1, "x": 70, "y": 230 }, { "idx": 2, "x": 130, "y": 150 }, { "idx": 3, "x": 190, "y": 150 }, { "idx": 4, "x": 250, "y": 230 }, { "idx": 5, "x": 310, "y": 300 } ], junctions: [ { x: null, y: null, points: [ { idx: 1, x: 111, y: 111 }, { idx: 2, x: 233, y: 323 }, { idx: 3, x: 422, y: 435 } ] } ], temPoints:[], isDraw:false, idx:0, className:"", isSelect:"" }, mounted:function(){ this.drawBaseLine(this.baseline);; this.drawLine(this.junctions); }, methods: { addClick(e){ this.className = e.target.className; }, //清屏 clearBoard(){ const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); let canvasW = ctx.canvas.clientWidth; let canvasH = ctx.canvas.clientHeight; this.junctions = []; ctx.clearRect(0, 0, canvasW, canvasH); this.drawBaseLine(this.baseline); this.drawContour(this.contour); }, //绘制基准线 drawBaseLine(baseline){ const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); if(baseline.length > 0){ for(let i = 0; i < baseline.length; i++){ if (i == 0) { ctx.beginPath(); ctx.strokeStyle = 'black'; ctx.moveTo(baseline[i].x, baseline[i].y); } else if (i == baseline.length - 1) { ctx.lineTo(baseline[i].x, baseline[i].y); ctx.stroke(); } else { ctx.lineTo(baseline[i].x, baseline[i].y); } } } }, //绘制轮廓线 drawContour(contour){}, //画线 junctions 多段线集合 drawLine(junctions) { const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); if(junctions.length > 0){ for(let i = 0; i < junctions.length; i++){ let points = junctions[i].points; for (let j = 0; j < points.length; j++) { if (j == 0) { ctx.beginPath(); if(this.isSelect === i){ ctx.strokeStyle = 'red'; }else{ ctx.strokeStyle = 'black'; } ctx.moveTo(points[j].x, points[j].y); } else if (j == points.length - 1) { ctx.lineTo(points[j].x, points[j].y); ctx.stroke(); } else { ctx.lineTo(points[j].x, points[j].y); } } } } }, //鼠标按下 drawLineMousedown(e){ if(this.className == 'pencil'){ let idx = this.idx; const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); // debugger; let offsetLeft = ctx.canvas.offsetLeft; let offsetTop = ctx.canvas.offsetTop; let x = e.clientX - offsetLeft,y = e.clientY - offsetTop; ctx.beginPath(); ctx.strokeStyle = 'black'; ctx.lineTo(x,y); this.isDraw = true; this.temPoints.push({idx,x,y}); this.idx ++; } }, //鼠标移动 //temPoints是存储画线的集合,junctions是默认返回的线+画线的集合 drawLineMousemove(e){ if(this.className == 'pencil'){ if(this.isDraw){ let idx = this.idx ++; const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); let offsetLeft = ctx.canvas.offsetLeft; let offsetTop = ctx.canvas.offsetTop; let x = e.clientX - offsetLeft,y = e.clientY - offsetTop; let last = this.temPoints[this.temPoints.length - 1]; //防止抖动移动问题 if (Math.sqrt(Math.pow(last.x - x, 2) + Math.pow(last.y - y, 2)) >= 5) { ctx.lineTo(x,y); ctx.stroke(); this.temPoints.push({idx,x,y}); } } } }, //鼠标抬起 drawFinish(){ if(this.className == 'pencil'){ const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); if(this.isDraw){ ctx.closePath(); //将数据存入集合,清空原有数据 let points = this.temPoints; this.temPoints = []; if (points.length > 1){ this.junctions.push({points}); } this.idx = 0; this.isDraw = false; } } }, //删除线 deleteLine(e){ if(this.className == 'delete'){ const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); let canvasW = ctx.canvas.clientWidth; let canvasH = ctx.canvas.clientHeight; //删除数据 if ((this.isSelect + '') != '' && this.isSelect >= 0) { this.junctions.splice(this.isSelect, 1); } this.isSelect = ""; //清空画板重新绘图 ctx.clearRect(0, 0, canvasW, canvasH); this.drawBaseLine(this.baseline); this.drawContour(this.contour); this.drawLine(this.junctions); } }, //选择要删除的线 select(e){ if(this.className == 'delete'){ const myCanvas = document.getElementById("myCanvas"); const ctx = myCanvas.getContext("2d"); let canvasW = ctx.canvas.clientWidth; let canvasH = ctx.canvas.clientHeight; let offsetLeft = ctx.canvas.offsetLeft; let offsetTop = ctx.canvas.offsetTop; let x = e.clientX - offsetLeft,y = e.clientY - offsetTop; let p = {x:x,y:y}; let result = this.pointInSegments(p,this.junctions); if(result.isonLine){ //在线上 //修改数据 this.isSelect = result.index; //清空画板重新绘图 ctx.clearRect(0, 0, canvasW, canvasH); this.drawBaseLine(this.baseline); this.drawContour(this.contour); this.drawLine(this.junctions); } } }, //判断是否选中线 pointInSegments(p, junctions) { let isonLine = false,index = ""; if(junctions.length>0){ for (let i = 0; i < junctions.length; i++ ){ let points = junctions[i].points; if(points.length>1){ for (let j = 1; j < points.length; j++ ){ let pi = points[j-1]; let pj = points[j]; if (this.isOnLine(pi, pj, p)) { isonLine = true; index = i; return {isonLine,index}; } } } } } return {isonLine,index}; }, isOnLine(p1, p2, p){ let minX = Math.min(p1.x, p2.x); // 较小的X轴坐标值 let maxX = Math.max(p1.x, p2.x); // 较大的X轴坐标值 let minY = Math.min(p1.y, p2.y); // 较小的Y轴坐标值 let maxY = Math.max(p1.y, p2.y); // 较大的Y轴坐标值 let offset = 10; //偏移量 if (p1.y === p2.y) { // 水平线 if ((p.x >= minX && p.x <= maxX) && (p.y >= minY - offset && p.y <= maxY + offset)) { return true; } return false; }else if (p1.x === p2.x) { // 垂直线 if ((p.y >= minY && p.y <= maxY) && (p.x >= minX - offset && p.x <= maxX + offset)) { return true; } return false; }else{ // 斜线 (先判断点是否进入可接受大范围(矩形),然后再根据直线上的交叉点进行小范围比较) if ((p.x >= minX && p.x <= maxX) && (p.y >= minY - offset && p.y <= maxY + offset)) { //求Y轴坐标 //方法1:根据tanθ= y/x = y1/x1, 即y = (y1/x1)*x (该方法有局限性,垂直线(p2.x - p1.x)=0,不能用) //var y = ((p2.y - p1.y) / (p2.x - p1.x)) * (px - p1.x); //方法2:先求弧度hudu,根据cosθ=x/r, r=x/cosθ,求得r,再根据sinθ=y/r, y=sinθ*r, 求得y let hudu = Math.atan2(p2.y - p1.y, p2.x - p1.x); // 直线的弧度(倾斜度) // 用三角函数计出直线上的交叉点 let r = (p.x - p1.x) / Math.cos(hudu); // 直角三角形的斜边(或理解成圆的半径) let y = Math.sin(hudu) * r; // Y轴坐标 let pm = { x: p.x, y: p1.y + y }; // 直线上的交叉点 if ((Math.abs(p.x - pm.x) <= offset) && (Math.abs(p.y - pm.y) <= offset)) { return true; } } return false; } }, } });