效果圖
如圖,代碼在最下面
前言
實現目標:使用canvas畫出地圖,且可以縮放,拖拽
個人理解:其實canvas動畫就是一次次的擦除重繪,就像是小時候看的動畫片,每一幀播的很快,因爲視覺停留,看起來像是連續動畫而已。
canvas 基礎
1.drawImage
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) //下面的變量名以此爲準
後面8個參數比較重要了:前 4 個是定義圖像源的切片位置和大小,後 4 個則是定義切片的目標顯示位置和大小。
貼個菜鳥鏈接 大概回顧下canvas drawImage
或者直接看下圖
拖拽功能
三個事件:onmousedown,onmousemove,onmouseup
大概講下思路,後面上代碼
- onmousedown 記錄鼠標按下的地址,且移動flag=true
- onmousemove 當移動flag爲true時,計算出drawImage需要的參數,並重繪
- onmouseup 將移動flag置爲false
邊界條件
當拖拽的時候不能無限的拖動,否則會超出畫布,所以需要限制一下 dx, dy, 兩個參數
放大與縮小
監聽滾輪事件:onmousewheel
- 縮放時以鼠標所在的點爲中心進行縮放,所以需要計算具體的drawImage的8個參數
- 每次選取圖像時就從(0,0)點選取全圖,sWidth, sHeight 即爲畫布大小不需要改變
- 假設需要變大N倍,那麼dWidth=NsWidth, dHeight=NsHeight
- 然後放置切片位置,dx,dy應始終爲負數,將圖像顯示到畫布中
如圖:紅色框是畫布, 畫布是不動的,我們根據這個看圖片怎麼放大。
- 圖1:原圖,
- 圖2:以原點放大N倍,
- 圖3:將放大的圖向左上平移,最終使放大的圖片在畫布中合適位置。
- 圖4:最終效果
其中最重要的就是計算左上平移,也就是dx,dy的數值。
定義變量:
- 放大倍數:imgScale
- 當前鼠標所在位置也就是縮放中心 pos:{x,y}
- 當前畫布左上移動的位置,即dx,dy的數值:imgX, imgY(默認爲0)
- 縮放後新的左上移動的位置,imgXN,imgYN
以下是公式:
imgXN = (1-imgScale)*(pos.x-imgX)/imgScale+(pos.x-(pos.x-imgX)/imgScale);
imgYN = (1-imgScale)*(pos.y-imgY)/imgScale+(pos.y-(pos.y-imgY)/imgScale);
上面公式沒有簡化🙃,其實如下
var newPos = {x:((pos.x-imgX)/imgScale).toFixed(2) , y:((pos.y-imgY)/imgScale).toFixed(2)};
imgXN = (1-imgScale)*newPos.x+(pos.x-newPos.x);
imgYN = (1-imgScale)*newPos.y+(pos.y-newPos.y);
以上公式不詳解了,大意就是:先把鼠標位置相對原圖的位置計算出來,然後以該點爲中心重新進行縮放(不知道有沒有更好的思路呢😊)
代碼
以下源碼:
<canvas id="scaleDragCanvas" width="878" height="547" style="border: thin solid #aaaaaa;"></canvas>
var canvas, context;
var img, imgX = 0, imgY = 0, imgScale = 1;
var MINIMUM_SCALE = 1.0 ,pos={},posl={},dragging = false;
(function int() {
canvas = document.getElementById('scaleDragCanvas'); //畫布對象
context = canvas.getContext('2d');//畫布顯示二維圖片
loadImg();
canvasEventsInit();
})();
function loadImg() {
img = new Image();
img.onload = function () {
drawImage();
}
img.src = 'https://static.zhihu.com/liukanshan/images/comics/bg-89c9bdc3.jpg';//劉看山
}
function drawImage() {
context.clearRect(0, 0, canvas.width, canvas.height);
// 保證 imgX 在 [img.width*(1-imgScale),0] 區間內
if(imgX<img.width*(1-imgScale)) {
imgX = img.width*(1-imgScale);
}else if(imgX>0) {
imgX=0
}
// 保證 imgY 在 [img.height*(1-imgScale),0] 區間內
if(imgY<img.height*(1-imgScale)) {
imgY = img.height*(1-imgScale);
}else if(imgY>0) {
imgY=0
}
context.drawImage(
img, //規定要使用的圖像、畫布或視頻。
0, 0, //開始剪切的 x 座標位置。
img.width, img.height, //被剪切圖像的高度。
imgX, imgY,//在畫布上放置圖像的 x 、y座標位置。
img.width * imgScale, img.height * imgScale //要使用的圖像的寬度、高度
);
}
/*事件註冊*/
function canvasEventsInit() {
canvas.onmousedown = function (event) {
dragging = true;
pos = windowToCanvas(event.clientX, event.clientY); //座標轉換,將窗口座標轉換成canvas的座標
};
canvas.onmousemove = function (evt) { //移動
if(dragging){
posl = windowToCanvas(evt.clientX, evt.clientY);
var x = posl.x - pos.x, y = posl.y - pos.y;
imgX += x;
imgY += y;
pos = JSON.parse(JSON.stringify(posl));
drawImage(); //重新繪製圖片
}
};
canvas.onmouseup = function () {
dragging = false;
};
canvas.onmousewheel = canvas.onwheel = function (event) { //滾輪放大縮小
var pos = windowToCanvas (event.clientX, event.clientY);
event.wheelDelta = event.wheelDelta ? event.wheelDelta : (event.deltalY * (-40)); //獲取當前鼠標的滾動情況
var newPos = {x:((pos.x-imgX)/imgScale).toFixed(2) , y:((pos.y-imgY)/imgScale).toFixed(2)};
if (event.wheelDelta > 0) {// 放大
imgScale +=0.1;
imgX = (1-imgScale)*newPos.x+(pos.x-newPos.x);
imgY = (1-imgScale)*newPos.y+(pos.y-newPos.y);
} else {// 縮小
imgScale -=0.1;
if(imgScale<MINIMUM_SCALE) {//最小縮放1
imgScale = MINIMUM_SCALE;
}
imgX = (1-imgScale)*newPos.x+(pos.x-newPos.x);
imgY = (1-imgScale)*newPos.y+(pos.y-newPos.y);
console.log(imgX,imgY);
}
drawImage(); //重新繪製圖片
};
}
/*座標轉換*/
function windowToCanvas(x,y) {
var box = canvas.getBoundingClientRect(); //這個方法返回一個矩形對象,包含四個屬性:left、top、right和bottom。分別表示元素各邊與頁面上邊和左邊的距離
return {
x: x - box.left - (box.width - canvas.width) / 2,
y: y - box.top - (box.height - canvas.height) / 2
};
}
立個flag,有空封裝一下