
需求:
默認后台返回的數據渲染到畫布上,然后用戶可以編輯重新畫線,並且可以點擊要移除的線條進行移除。
現在做的交互是選中需要移除的線條高亮顯示,然后雙擊進行移除。
<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;
}
},
}
});
