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,中提供的組件無需關注,做前端都應該知道,感興趣可去官網看看。
篇幅較長,如有需求請仔細閱讀定會有所幫助(不懂之處,可在評論處提問),代碼有很多冗余部分,有待優化,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM