国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

這篇具有很好參考價值的文章主要介紹了Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動

本文會帶大家認(rèn)識Canvas中常用的坐標(biāo)變換方法 translate 和 scale,并結(jié)合這兩個方法,實現(xiàn)鼠標(biāo)滾輪縮放以及畫布拖動功能。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

Canvas的坐標(biāo)變換

Canvas 繪圖的縮放以及畫布拖動主要通過 CanvasRenderingContext2D 提供的 translatescale 兩個方法實現(xiàn)的,先來認(rèn)識下這兩個方法。

translate 方法

語法:

translate(x, y)

translate 的用法記住一句話:

translate 方法重新映射畫布上的(0, 0)位置。

說白了就是把畫布的原點移動到了 translate 方法指定的坐標(biāo),之后所有圖形的繪制都會以該坐標(biāo)進(jìn)行參照。

舉個例子:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 600;
canvas.height = 400;

ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);

ctx.translate(50, 50);

ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);

開始的時候,Canvas 容器原點和繪圖原點重合,繪制一個背景色為紅色,原點坐標(biāo)(50, 50),長寬各為 50 的矩形,接著調(diào)用 translate 方法將繪圖原點沿水平和縱向各偏移50,再繪制一個背景色是綠色,原點坐標(biāo)(50, 50),長寬各為 50 的矩形,示意圖如下,其中灰色的背景為 Canvas 區(qū)域。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

需要注意的是,如果此時繼續(xù)調(diào)用 translate 方法進(jìn)行偏移操作,后續(xù)的偏移會基于原來偏移的基礎(chǔ)上進(jìn)行的。

ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);

// 第一次坐標(biāo)系偏移
ctx.translate(50, 50);

ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);

// 第二次坐標(biāo)系偏移
ctx.translate(50, 50);

ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 50, 50);

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

因此,如果涉及到多次調(diào)用 translate 方法進(jìn)行坐標(biāo)變換,很容易將坐標(biāo)系搞混亂,所以,一般在translate 之前會調(diào)用 save 方法先保存下繪圖的狀態(tài),再調(diào)用 translate 后,繪制完圖形后,調(diào)用 restore 方法恢復(fù)之前的上下文,對坐標(biāo)系進(jìn)行還原,這樣不容易搞亂坐標(biāo)系。

save方法通過將當(dāng)前狀態(tài)壓入堆棧來保存畫布的整個狀態(tài)。

保存到堆棧上的圖形狀態(tài)包括:

  • 當(dāng)前轉(zhuǎn)換矩陣。
  • 當(dāng)前裁剪區(qū)域。
  • 當(dāng)前的破折號列表。
  • 包含的屬性:strokeStyle、ill Style、lobalAlpha、linewidth、lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、global alCompositeOperation、Font、extAlign、extBaseline、Direction、ImageSmoothingEnabled。

restore 方法通過彈出繪制狀態(tài)堆棧中的頂部條目來恢復(fù)最近保存的畫布狀態(tài)。

ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);

// 保存繪圖上下文
ctx.save()

ctx.translate(50, 50);
ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);

// 繪制完成后恢復(fù)上下文
ctx.restore()
 
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 50, 50);

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

scale 方法

語法:

scale(x, y)

縮放 (scale) 就是將一個圖形圍繞中心點,然后將寬和高分別乘以一定的因子(sx,sy)

默認(rèn)情況下,畫布上的一個單位正好是一個像素??s放變換會修改此行為。例如,如果比例因子為0.5,則單位大小為0.5像素;因此,形狀的繪制大小為正常大小的一半。類似地,比例因子為2會增加單位大小,使一個單位變?yōu)閮蓚€像素;從而以正常大小的兩倍繪制形狀。

舉個例子:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

ctx.scale(0.5,2);
ctx.fillStyle="blue";
ctx.fillRect(50,50,100,50);

調(diào)用?scale(0.5,2)?將畫布水平方向縮小一倍,垂直方向放大一倍,繪制一個坐標(biāo)原點 (50, 50),寬度 100,高度 50 的矩形。經(jīng)過縮放變換后,距離原點的實際像素是橫軸 25像素,縱軸 100 像素,寬度 50 像素,高度 100 像素。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

實現(xiàn)鼠標(biāo)拖動畫布

效果

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

創(chuàng)建Sence類

Sence類:

class Scene {
  constructor(id, options = {
    width: 600,
    height: 400
  }) {
    this.canvas = document.querySelector('#' + id)
    this.width = options.width;
    this.height = options.height;
    this.canvas.width = options.width;
    this.canvas.height = options.height;
    this.ctx = this.canvas.getContext('2d');
  }

  draw() {
    this.ctx.fillStyle = 'red';
    this.ctx.fillRect(50, 50, 50, 50);
    this.ctx.fillStyle = 'green';
    this.ctx.fillRect(150, 150, 50, 50);
  }

  clear() {
    this.canvas.width = this.width;
  }

  paint() {
    this.clear();
    this.draw();
  }
}

let scene = new Scene('canvas');

scene.draw();

Sence 類的構(gòu)造函數(shù)中初始化 Canvas,得到 CanvasRenderingContext2D 對象,并設(shè)置 Canvas 的寬高屬性,draw 方法里面繪制了兩個矩形。

在進(jìn)行下面的工作之前,我們先來了解下 Canvas 的事件機(jī)制。

通過 addEventListener 方法可以給 Canvas 綁定一個事件。

this.canvas.addEventListener('mousedown', (event) => {
  console.log(event.x)
});

事件的回調(diào)函數(shù)參數(shù)的 event 對象中可以獲取鼠標(biāo)點擊 Canvas 時的坐標(biāo)信息,event 對象中經(jīng)常會用到的坐標(biāo)有兩個,一個是 event.xevent.y,另一個是 event.offsetXevent.offsetY,其中,event.xevent.y 獲取的是鼠標(biāo)點擊時相對于屏幕的坐標(biāo),而 event.offsetXevent.offsetY 是相對于 Canvas 容器的坐標(biāo)。

通過下面這張圖可以清晰的看出兩個坐標(biāo)的區(qū)別,明白這一點對于我們后續(xù)的坐標(biāo)變換非常重要。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

在構(gòu)造函數(shù)中添加對 Canvasmousedown 事件監(jiān)聽,記錄點擊鼠標(biāo)時相對屏幕的位置 xy。

class Scene {
  x = 0; // 記錄鼠標(biāo)點擊Canvas時的橫坐標(biāo)
  y = 0; // 記錄鼠標(biāo)點擊Canvas時的縱坐標(biāo)
  constructor(id, options = {
    width: 600,
    height: 400
  }) {
    this.canvas.addEventListener('mousedown', this.onMousedown);
  }
  
  onMousedown(e) {
    if (e.button === 0) {
      // 點擊了鼠標(biāo)左鍵
      this.x = x;
      this.y = y;
    }
  }
}

畫布拖動的整體思路就是利用前面介紹的 Canvastranslate 方法。畫布的整體偏移量記錄在 offset.xoffset.y,鼠標(biāo)觸發(fā) mousedown 事件時,記錄當(dāng)前鼠標(biāo)點擊的位置相對于屏幕的坐標(biāo) x, 和 y,并且開始監(jiān)聽鼠標(biāo)的 mousemovemouseup 事件。鼠標(biāo)觸發(fā) mousemove 事件時計算每次移動時整體累加的偏移量:

onMousemove(e) {
  this.offset.x = this.curOffset.x + (e.x - this.x);
  this.offset.y = this.curOffset.y + (e.y - this.y);
  this.paint();
}

其中 curOffset.xcurOffset.y 記錄的是鼠標(biāo)觸發(fā) mouseup 時保存的當(dāng)前的偏移量,便于計算累加的偏移量。每次觸發(fā)完鼠標(biāo) mousemove 事件后,重新進(jìn)行圖形繪制。

onMouseup() {
  this.curOffset.x = this.offset.x;
  this.curOffset.y = this.offset.y;
  window.removeEventListener('mousemove', this.onMousemove);
  window.removeEventListener('mouseup', this.onMouseup);
}

Sence 類完整代碼如下:

class Scene {
  offset = { x: 0, y: 0 }; // 拖動偏移
  curOffset = { x: 0, y: 0 }; // 記錄上一次的偏移量
  x = 0; // 記錄鼠標(biāo)點擊Canvas時的橫坐標(biāo)
  y = 0; // 記錄鼠標(biāo)點擊Canvas時的縱坐標(biāo)

  constructor(id, options = {
    width: 600,
    height: 400
  }) {
    this.canvas = document.querySelector('#' + id);
    this.width = options.width;
    this.height = options.height;
    this.canvas.width = options.width;
    this.canvas.height = options.height;
    this.ctx = this.canvas.getContext('2d');
    this.onMousedown = this.onMousedown.bind(this);
    this.onMousemove = this.onMousemove.bind(this);
    this.onMouseup = this.onMouseup.bind(this);
    this.canvas.addEventListener('mousedown', this.onMousedown);
  }

  onMousedown(e) {
    if (e.button === 0) {
      // 鼠標(biāo)左鍵
      this.x = e.x;
      this.y = e.y
      window.addEventListener('mousemove', this.onMousemove);
      window.addEventListener('mouseup', this.onMouseup);
    }
  }

  onMousemove(e) {
   this.offset.x = this.curOffset.x + (e.x - this.x);
   this.offset.y = this.curOffset.y + (e.y - this.y);

   this.paint();
  }

  onMouseup() {
    this.curOffset.x = this.offset.x;
    this.curOffset.y = this.offset.y;
    window.removeEventListener('mousemove', this.onMousemove);
    window.removeEventListener('mouseup', this.onMouseup);
  }

  draw() {
    this.ctx.fillStyle = 'red';
    this.ctx.fillRect(50, 50, 50, 50);

    this.ctx.fillStyle = 'green';
    this.ctx.fillRect(150, 150, 50, 50);
  }

  clear() {
    this.canvas.width = this.width;
  }

  paint() {
    this.clear();
    this.ctx.translate(this.offset.x, this.offset.y);
    this.draw();
  }
}

上述代碼中有幾點需要注意:

  1. 事件函數(shù)中的this指向問題

細(xì)心的同學(xué)可能注意到,在 Sence 類的構(gòu)造函數(shù)里有這樣幾行代碼:

constructor(id, options = {
    width: 600,
    height: 400
  }) {
    this.onMousedown = this.onMousedown.bind(this);
    this.onMousemove = this.onMousemove.bind(this);
    this.onMouseup = this.onMouseup.bind(this);
  }

為什么要使用 bind 函數(shù)給事件函數(shù)重新綁定this對象呢?

主要的原因在于一個事件有監(jiān)聽就會有移除。假設(shè)我們想要銷毀 mousemove 事件怎么辦呢?

可以調(diào)用 removeEventListener 方法進(jìn)行事件監(jiān)聽的移除,比如上述代碼會在 onMouseup 中移除對 mousemove 事件的監(jiān)聽:

onMouseup() {
  this.curOffset.x = this.offset.x;
  this.curOffset.y = this.offset.y;
  window.removeEventListener('mousemove', this.onMousemove);
}

如果不在構(gòu)造函數(shù)中使用 bind 方法重新綁定 this 指向,此時的 this 指向的就是window,因為 this 指向的是調(diào)用 onMouseup 的對象,而 onMouseup 方法是被 window 上的 mouseup 事件調(diào)用的,但是實際上我們想要的this指向應(yīng)該 Sence 實例。為了避免上述問題的出現(xiàn),最好的解決辦法就是在 Sence 類的構(gòu)造函數(shù)中重新綁定 this 指向。

  1. 畫布的清空問題

每次鼠標(biāo)移動的時候會改變 CanvasCanvasRenderingContext2D 偏移量,并重新進(jìn)行圖形的繪制,重新繪制的過程就是先將畫布清空,然后設(shè)置畫布的偏移量(調(diào)用 translate 方法),接著繪制圖形。其中清空畫布這里選擇了重新設(shè)置Canvas的寬度,而不是調(diào)用 clearRect 方法,主要是因為clearRect 方法只在 Canvas 的渲染上下文沒有進(jìn)行過平移、縮放、旋轉(zhuǎn)等變換時有效,如果 Canvas 的渲染上下文已經(jīng)經(jīng)過了變換,那么在使用 clearRect 清空畫布前,需要先重置變換,否則 clearRect 將無法有效地清除整塊畫布。

實現(xiàn)鼠標(biāo)滾輪縮放

效果

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

實現(xiàn)原理

鼠標(biāo)滾輪的放大需要結(jié)合上面介紹的 Canvastranslatescale 兩個方法進(jìn)行組合變換。

計算放大系數(shù)

監(jiān)聽鼠標(biāo)滾輪的 mousewheel 事件,在事件的回調(diào)函數(shù)中通過 event.wheelDelta 值的變化來實時計算當(dāng)前的縮放值,其中 event.wheelDelta > 0 表示放大,反之表示縮小,放大和縮小都有對應(yīng)的閾值,超過閾值就禁止繼續(xù)放大和縮小。

改造 Sence 類,添加 onMousewheel 事件:

onMousewheel(e) {
  if (e.wheelDelta > 0) {
    // 放大
    this.scale = parseFloat((this.scaleStep + this.scale).toFixed(2)); // 解決小數(shù)點運算丟失精度的問題
    if (this.scale > this.maxScale) {
      this.scale = this.maxScale;
      return;
    }
  } else {
    // 縮小
    this.scale = parseFloat((this.scale - this.scaleStep).toFixed(2)); // 解決小數(shù)點運算丟失精度的問題
    if (this.scale < this.minScale) {
      this.scale = this.minScale;
      return;
    }
  }
  
  this.preScale = this.scale;
}

其中,this.scale / this.preScale 計算出來的值就是放大系數(shù),暫且記做 n

在計算放大系數(shù)的時候,需要注意兩個浮點型數(shù)值在計算不能直接相加,否則會出現(xiàn)丟失精度的問題。

縮放原理

在縮放的時候,會調(diào)用 scale(n, n) 方法,將坐標(biāo)系放大 n 倍。假設(shè)鼠標(biāo)滾輪停在 A 點進(jìn)行放大操作,放大之后得到坐標(biāo) A’ 點。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

可以看到,放大之后,A(x1, y1) 坐標(biāo)變換到了 A'(x1, y1),A => A' 放大了 n 倍,因此得到 x1 = x * n,y1 = y1 * n

這個時候就會存在一個問題,我們在 A 點進(jìn)行放大,放大后得到的 A' 的位置應(yīng)該是不變的,所以需要在放大之后需要調(diào)整 A’ 點的位置到 A 點。

這里我們采用的策略是在放大前先偏移一段距離,然后進(jìn)行放大之后就可以保持 A 點和 A‘ 點的重合。

Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)

鼠標(biāo)停留在 A 點對藍(lán)色矩形進(jìn)行放大,放大系數(shù)為 n,藍(lán)色矩形的起點左上角和坐標(biāo)原點重合,寬度和高度分別是 xy,因此,A點的坐標(biāo)為 (x, y)。

前面我們說過,對 A 點進(jìn)行放大后得到的 A’點應(yīng)該和A點重合,這樣就需要先把整個坐標(biāo)系沿著x軸和y軸分別向左和向上偏移 offsetXoffsetY,偏移后得到的 A'點坐標(biāo)記作 (x1, x2),因為 A 點是經(jīng)過放大 n 倍后得到的 A' 點,所以得到以下距離關(guān)系:

x1 = x * n;
y1 = y * n

進(jìn)一步就可以得到橫縱坐標(biāo)的偏移量 offsetXoffsetY 的絕對值:

offsetX = x*n-x;
offsetY =x*n - y;

因此,這需要將坐標(biāo)系經(jīng)過 translate(-offsetX, -offsetY) 之后,再 scale(n, n),就能確保 A 點 和 A‘ 點重合了。

明白了縮放的基本原理,下面就繼續(xù)碼代碼吧??。

onMousewheel(e) {
  e.preventDefault();

  this.mousePosition.x = e.offsetX; // 記錄當(dāng)前鼠標(biāo)點擊的橫坐標(biāo)
  this.mousePosition.y = e.offsetY; // 記錄當(dāng)前鼠標(biāo)點擊的縱坐標(biāo)
  if (e.wheelDelta > 0) {
    // 放大
    this.scale = parseFloat((this.scaleStep + this.scale).toFixed(2)); // 解決小數(shù)點運算丟失精度的問題
    if (this.scale > this.maxScale) {
      this.scale = this.maxScale;
      return;
    }
  } else {
    // 縮小
    this.scale = parseFloat((this.scale - this.scaleStep).toFixed(2)); // 解決小數(shù)點運算丟失精度的問題
    if (this.scale < this.minScale) {
      this.scale = this.minScale;
      return;
    }
  }

  this.offset.x = this.mousePosition.x - ((this.mousePosition.x -   this.offset.x) * this.scale) / this.preScale;
  this.offset.y = this.mousePosition.y - ((this.mousePosition.y - this.offset.y) * this.scale) / this.preScale;

  this.paint(this.ctx);
  this.preScale = this.scale;
  this.curOffset.x = this.offset.x;
  this.curOffset.y = this.offset.y;
}

paint() {
  this.clear();
  this.ctx.translate(this.offset.x, this.offset.y);
  this.ctx.scale(this.scale, this.scale);
  this.draw();
}

總結(jié)

本文從基礎(chǔ)原理到代碼實現(xiàn),完整給大家講解了 Canvas 畫布繪制中經(jīng)常會遇到的畫布拖動和鼠標(biāo)滾輪縮放功能,希望對大家有幫助,更多精彩文章歡迎大家關(guān)注我的vx公眾號:前端架構(gòu)師筆記。本文完整代碼地址:https://github.com/astonishqft/scanvas-translate-and-scale文章來源地址http://www.zghlxwxcb.cn/news/detail-406908.html

到了這里,關(guān)于Canvas鼠標(biāo)滾輪縮放以及畫布拖動(圖文并茂版)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包