1. 程式人生 > >canvas上畫出座標集合,並標記新座標,背景支援放大縮小拖動功能

canvas上畫出座標集合,並標記新座標,背景支援放大縮小拖動功能

寫在前面:專案需求,使用者上傳一個區位的平面圖片,使用者可以在圖片上新增新的相機位置,並且展示之前已繫結的相機座標位置,圖片支援放大縮小&拖動的功能。新增座標,頁面展示相對canvas定位,儲存時儲存該座標在背景圖片上的座標。原有座標集合相對背景圖片定位,(圖片放大縮小或者拖動時始終在圖片的固定位置)。

需求難點:1.不同座標(原有座標集合/新增座標處理方式不同,座標計算方式不同)2.圖片在放大縮小/拖動時座標計算問題。

廢話不多說,專案使用vue開發的,直接貼程式碼

注:因專案是用vue開發的,this 指向均為vue例項,this/this_  後面的變數,均在該元件data中已經定義,變數名稱基本明確,不明確的地方會註釋出來

① imgX,imgY 背景圖片原點(左上角)座標,因為初始化為平鋪畫布,為(0,0),

②imgScale 圖片放大倍數,

 

/*-----初始化畫布獲取canvas節點-----*/
formatMap () {
this.canvas = document.getElementById('canvas');
this.context = this.canvas.getContext('2d');
this.loadImg()
},
/*-----載入圖片方法-----*/
loadImg () {
this.canvasWidth = this.canvas.width; // canvas畫布寬度

this.canvasHeight = this.canvas.height; // canvas互補畫布
let this_ = this;
this.img = new Image();
this.img.onload = function(){
this_.imgIsLoaded = true;
console.log(this_.img.width);
// this_.imgWidthHeightRatio = this_.img.width/this_.img.height; 原計劃是畫布寬高比根據所上傳的圖片確定修改為固定比例
this_.ratioImgCanvas = this_.img.width/this_.canvasWidth; // 圖片的寬度/canvas畫布寬度

this_.ratioImgCanvas1 = this_.img.height/this_.canvasHeight; // 圖片的高度/canvas畫布高度

for (let i=0; i<this_.resPosArr.length;i++) { // resPosArr 為後端取來的座標陣列 longitude 橫座標x,latitude 縱座標y,包含一些其他相機資料
this_.initArr[i] ={}; // 初始化原有座標集合,相對背景定位 eg:[{x:110,y:110},{x:220,y:220},{x:330,y:330},{x:110,y:69}];
      this_.initArr[i].x = (this_.resPosArr[i].longitude)/this_.ratioImgCanvas;    // 根據 圖片與canvas寬高比換算出,當圖片平鋪在canvas上時,座標在canvas上的座標,方便繪圖函式繪製座標為位置,(放大縮小時需要原有座標,該陣列值不會根據慚怍變化而變化,放大縮小或者拖動)。
this_.initArr[i].y = (this_.resPosArr[i].latitude)/this_.ratioImgCanvas1;
}
console.log(this_.initArr);
for (let k=0;k<this_.initArr.length;k++) { // iconArr 繪圖函式繪圖時座標為位置,放大縮小/拖動,座標在canvas上的座標會一直變化(相對背景圖片不會變化)
this_.iconArr[k] = {};
this_.iconArr[k].x = this_.initArr[k].x;
this_.iconArr[k].y = this_.initArr[k].y;
}
this_.drawImageCanvas(); // 呼叫繪圖函式
}
this.img.src = this.imgSrc; //向量圖
/*icon圖片物件*/
this.icon = new Image();
this.icon.onload = function(){
this_.iconIsLoaded = true;
this_.drawImageCanvas();
}
this.icon.src = "../../../../static/img/map/already_edit.png"; // 地圖上原有的圖示
this.addIcon = new Image ();
this.addIcon.onload = function () {
}
this.addIcon.src = '../../../../static/img/map/current_edit.png';// 地圖上新增的圖示
console.log(this.addIcon);
},
/*-----繪圖函式-----*/
drawImageCanvas (x,y) {
console.log('執行次數');
this.context.clearRect(0,0,this.canvas.width,this.canvas.height); // 清空背景
// 畫背景
this.context.drawImage(this.img,0,0,this.img.width,this.img.height,this.imgX,this.imgY,this.canvas.width*this.imgScale,this.canvas.height*this.imgScale);
for (let i = 0;i < this.iconArr.length;i ++) {
this.context.drawImage(this.icon,this.iconArr[i].x-16,this.iconArr[i].y-16,32,32) // 畫原有座標(座標集合)
}
/*未點選新增座標時,不繪製新增icon*/
if (x !== undefined && y !== undefined && x !== '' && y !== '') {
this.context.drawImage(this.addIcon,x-16,y-16,32,32) // 畫新增座標
}else if (this.addIconX !== '' && this.addIconY !== ''){
this.context.drawImage(this.addIcon,this.addIconX-16,this.addIconY-16,32,32) // 畫新增座標
}
},
/*-----滑鼠按下/拖動/擡起事件-----*/
mouseEvent () {
let this_ = this;
this.canvas.onmousedown = function (event) {
let pos0 = this_.windowToCanvas(this_.canvas,event.clientX,event.clientY); // 呼叫視窗座標轉canvas座標函式
let pos = this_.windowToCanvas(this_.canvas,event.clientX,event.clientY);
this_.canvas.onmousemove = function (event) {
let pos1 = this_.windowToCanvas(this_.canvas,event.clientX,event.clientY);
/*按下座標在新增座標上,拖動座標位置or拖動背景圖片*/ // 注:此處為操作邏輯處理,當按下位置在新增座標上,拖動時 拖動的是新增座標,背景不會動,否則拖動背景
   // 座標為一個點,畫布上以座標為中心點畫一個32px*32px的正方形,這樣就有了滑鼠按下的判斷區間
if (pos.x > this_.addIconX1-16 && pos.x < this_.addIconX1+16 && pos.y > this_.addIconY1-16 && pos.y <this_.addIconY1+16) {
     // 當拖動新增座標時 ,滑鼠位置為新增座標位置
this_.addIconX2 = pos1.x;
this_.addIconY2 = pos1.y;
this_.drawImageCanvas(this_.addIconX2,this_.addIconY2);
}else{ // 滑鼠按下的位置在畫布上(不在新增座標上)
/*滑鼠移出canvas畫布外=> 禁止拖動*/
if (pos1.x >= this_.canvas.width-20 || pos1.y >= this_.canvas.height-20 || pos1.x <=20 || pos1.y<=20) {
this_.canvas.onmousemove = null;
return false;
}
let x = pos1.x-pos.x; // 滑鼠按下座標與移動座標差值
let y = pos1.y-pos.y;
pos = pos1;
this_.imgX += x;
this_.imgY += y;
/*背景拖到邊緣時禁止拖動*/
if (this_.imgX >= 0) {
this_.imgX =0;
}
if (this_.imgY >= 0) {
this_.imgY = 0;
}
if (-(this_.imgX) >= (this_.canvas.width * this_.imgScale - this_.canvas.width )) {
this_.imgX = -(this_.canvas.width * this_.imgScale - this_.canvas.width)
}
if (-(this_.imgY)>=(this_.canvas.height * this_.imgScale - this_.canvas.height )) {
this_.imgY = -(this_.canvas.height * this_.imgScale - this_.canvas.height)
}
// console.log('畫布原點',this_.imgX,this_.imgY);
for (let j = 0;j < this_.iconArr.length;j++) { // 座標集合移動處理 此處為拖動時座標處理 => 不理解可以在紙上畫畫,確定座標資料計算方式
this_.iconArr[j].x = this_.imgX + this_.initArr[j].x;
this_.iconArr[j].y = this_.imgY + this_.initArr[j].y;
}
this_.drawImageCanvas(this_.addIconX1,this_.addIconY1); // 繪製圖片/ 新增座標相對canvas不變化 addIconX1,在拖動時為定值,拖動時在canvas上的位置沒變化
}
}
this_.canvas.onmouseup = function(event){
let pos2 = this_.windowToCanvas(this_.canvas,event.clientX,event.clientY);
this_.canvas.style.cursor = "default";
this_.canvas.onmousemove = this_.cnvs_getCoordinates; // 滑鼠在canvas上越過時觸發函式,為實現其他功能暫時忽略
this_.canvas.onmouseup = null;

/*滑鼠按下擡起時,是否產生移動=》是:拖動事件,否:點選事件*/
let x1 = pos2.x - pos0.x;
let y1 = pos2.y - pos0.y;
if (x1 ===0 && y1 === 0) {
console.log("點選事件");
for (let i = 0;i<this_.iconArr.length;i++) {
if (pos2.x >= this_.iconArr[i].x-16 && pos2.x <= this_.iconArr[i].x+16 && pos2.y >= this_.iconArr[i].y-16 && pos2.y <= this_.iconArr[i].y+16) {
this_.handleCanvasIconClick(i,pos2) // 點選時 當點選在原有座標上可進行修改操作呼叫函式 暫時忽略
}
}
}else {
console.log("拖動事件");
/*當拖動新增座標時,滑鼠移動最終位置位置,為新增座標最終位置 => 注意點*/
if (pos.x > this_.addIconX1-16 && pos.x < this_.addIconX1+16 && pos.y > this_.addIconY1-16 && pos.y <this_.addIconY1+16) {
this_.addIconX1 = this_.addIconX2;
this_.addIconY1 = this_.addIconY2;
}
}
}
}
},

/*-----點選放大按鈕->以中心點放大-----*/
// 放大方法圖片是以中心點放大 縮小同理,計算方式比較簡單
magnifyFn () {
this.alertMsgBox.style.display = 'none';
if (this.imgScale<3.6) {
this.imgScale *= 1.2;
this.imgX = (this.canvas.width - this.canvas.width * this.imgScale) / 2;
this.imgY = (this.canvas.height - this.canvas.height * this.imgScale) / 2;
  // 放大縮小需處理 原有座標集合,注意點
for (let i=0;i<this.initArr.length;i++) {
this.initArr[i].x = this.initArr[i].x * 1.2;
this.initArr[i].y = this.initArr[i].y * 1.2;
}
for (let j=0;j<this.iconArr.length;j++) {
this.iconArr[j].x = this.imgX + this.initArr[j].x;
this.iconArr[j].y = this.imgY + this.initArr[j].y;
}
this.drawImageCanvas(this.addIconX2,this.addIconY2);
return false;
}else{
this.$alert('已經最大了', {
confirmButtonText: '確定',
center:true
});
}
},
/*-----點選縮小按鈕,以中心點縮小-----*/
shrinkFn () {
this.alertMsgBox.style.display = 'none';
if (this.imgScale>1) {
this.imgScale /= 1.2;
this.imgX = (this.canvas.width - this.canvas.width * this.imgScale) / 2;
this.imgY = (this.canvas.height - this.canvas.height * this.imgScale) / 2;

for (let i=0;i<this.initArr.length;i++) {
this.initArr[i].x = this.initArr[i].x / 1.2;
this.initArr[i].y = this.initArr[i].y / 1.2;
}
for (let j=0;j<this.iconArr.length;j++) {
this.iconArr[j].x = this.imgX + this.initArr[j].x;
this.iconArr[j].y = this.imgY + this.initArr[j].y;
}
this.drawImageCanvas(this.addIconX2,this.addIconY2);
// console.log('畫布原點',this.imgX,this.imgY);
}else {
this.$alert('已經最小了', {
confirmButtonText: '確定',
center:true
});
}
return false;
},

/*-----選擇相機後新增座標-----*/
// 點選新增座標按鈕後,新增座標初始位置在canvas畫布中心
addBtnClick () {
this.addIconX = this.canvas.width/2;
this.addIconY = this.canvas.height/2;
this.drawImageCanvas(this.addIconX,this.addIconY);
this.addIconX1 = this.addIconX;
this.addIconY1 = this.addIconY;
},
/*-----關聯相機頁面-點選儲存按鈕-----*/
saveBtnClick () {
console.log(this.locationMapDetail);
//if (this.locationMapDetail.img == null) {
//this.$alert('該區位暫未上傳圖片,請先上傳圖片', {
//confirmButtonText: '確定',
//center:true
//});
//return
//}
console.log("當前放大倍數",this.imgScale);

// 前面對於新增座標的處理,座標都是 新增座標點在canvas上的位置,儲存時需要通過計算,得到該座標在背景圖片的位置,儲存的也將是計算得來的座標值。
// 計算這裡我是分為兩步,稍微有點繞,其實仔細想想也並不難,需多在紙上畫畫計算一下。
this.fx = ((this.addIconX1) - this.imgX)/this.imgScale;
this.fy = ((this.addIconY1) - this.imgY)/this.imgScale;
// console.log(this.fx,this.fy);
this.sx = (this.fx*this.img.width)/this.canvas.width;
this.sy = (this.fy*this.img.height)/this.canvas.height;
// alert("儲存的座標點為" + this.sx + " : " + this.sy);
this.$post(this.baseUrl + ' 後端介面 ?locationId='+ this.selectedMapId +
'&cameraId=' + this.selectedCameraId + '&longitude='+ this.sx+'&latitude=' + this.sy)
.then((res)=>{
console.log(res);
if (res.data.code === '200' && res.data.msg === 'success') {
this.$notify.success({
message: '儲存成功',
offset: 600
});
/*儲存成功重新載入地圖*/
this.initArr = [];
this.$fetch(this.baseUrl + '/介面地址/' + this.selectedMapId + '?withBlob=true&enableCoordinate=true').then((res)=> {
console.log(res);
if (res.data.img == null) {
this.imgSrc = '../../../../static/img/map/zhanweitu.png'
} else {
this.imgSrc = res.data.img;
}
if (res.data.cameraMapResourceDtos != null) {
this.resPosArr = res.data.cameraMapResourceDtos;
this.cameraCount = this.resPosArr.length;
} else {
this.resPosArr = [];
this.cameraCount = this.resPosArr.length;
}
})
}else {
this.$notify({
message: '儲存失敗',
offset: 600,
type: 'warning'
});
}
})
},
/*-----window座標轉canvas座標-----*/
windowToCanvas (canvas,x,y) {
let bbox = canvas.getBoundingClientRect();
return {
x:x - bbox.left - (bbox.width - canvas.width) / 2,
y:y - bbox.top - (bbox.height - canvas.height) / 2
};
},

寫在最後:這個需求,對於像我這樣的canvas新手會有點難度,不過通過找找資料基本也能實現需求的功能。原專案是vue專案,貼的程式碼中省區了部分頁面互動的程式碼(不然就有點太多了),
程式碼中this.$notify this.$alert,只是element-ui,中提供的元件無需關注,做前端都應該知道,感興趣可去官網看看。
篇幅較長,如有需求請仔細閱讀定會有所幫助(不懂之處,可在評論處提問),程式碼有很多冗餘部分,有待優化,歡迎指正。