摘要:如何在中添加事件作為一個前端,給元素添加事件是一件司空見慣的事情。看到這個參數,我開始以為是或者的返回值,很可惜的是這兩個方法并沒有返回值,在查閱了資料后,發現是構造函數的對象。構造函數具體用法。
如何在Canvas中添加事件
作為一個前端,給元素添加事件是一件司空見慣的事情。可是在Canvas中,其所畫的任何東西都是無法獲取的,更別說添加事件,那么我們對其就束手無策了嗎?當然不是的!我們在平時項目中肯定都用過許多Canvas的框架,我們發現事件在這些框架中已經使用的十分成熟了,而且并沒有出現特別嚴重的問題。那么我們可以肯定的是,事件在Canvas中并不是一個無法觸及的事情。
一個傻瓜式的方式我們都知道一個元素在觸發一個事件時,其鼠標的位置基本處于該元素之上,那么我們就自然而然的想到通過當前鼠標的位置以及物體所占據的位置進行比對,從而我們就能得出該物體是否應觸發事件。這種方式比較簡單,我就不用代碼演示了,不過既然我叫它傻瓜式的方式,很明顯它不是一個有效的解決方式。因為物體所占據的位置并不一定是十分容易獲取,如果是矩形、圓形等我們還能通過一些簡單的公式獲取其占據的位置,可是在復雜點的多邊形,甚至是多邊形的某些邊是弧線的,顯而易見,我們這時候再獲取其所占據的位置時是一件極其復雜且難度極大的事情,所以這種方式只適合自己在做一些demo中使用,并不適用于大多數的情況。
一個較聰明的方式既然上面這種方式碰壁了,那么我們只能另辟蹊徑。在翻閱CanvasAPI的時候,找到了一個方法isPointInPath,貌似正是我們苦苦尋找的良藥。
介紹isPointInPathisPointInPath的作用:顧名思義,我們很直觀的可以知道該方法用以判斷點是否處于路徑當中。
isPointInPath的入參出參:ctx.isPointInPath([path, ]x, y [, fillRule]),該方法的參數有4個,其中path和fillRule為選填,x和y為必填。我們依次介紹4個參數。
path:看到這個參數,我開始以為是beginPath或者closePath的返回值,很可惜的是這兩個方法并沒有返回值,在查閱了資料后,發現是Path2D構造函數new的對象。Path2D構造函數具體用法。不過可惜的是該方法可能由于兼容性的問題,目前看了一些開源框架都還未使用。
x,y:這兩個參數很好理解,就是x軸和y軸的距離,需要注意的是,其相對位置是Canvas的左上角。
fillRule:nonzero(默認),evenodd。非零環繞規則和奇偶規則是圖形學中判斷一個點是否處于多邊形內的規則,其中非零環繞規則是Canvas的默認規則。想具體了解這兩種規則的,可以自己去查閱資料,這里就不增加篇幅介紹了。
上面介紹完了入參,那么isPointInPath方法的出參想必大家都可以猜到了,就是true和false。
使用isPointInPath上一節介紹完isPointInPath方法后,我們現在就來使用它吧。
先來一個簡單的demo:
const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d") ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10, 50) ctx.lineTo(50, 50) ctx.lineTo(50, 10) ctx.fillStyle= "black" ctx.fill() ctx.closePath() canvas.addEventListener("click", function (e) { const canvasInfo = canvas.getBoundingClientRect() console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top)) })
如圖所示,灰色部分為Canvas所占據的區域,黑色為我們實際添加事件的區域,在我們點擊黑色區域后,實際也的確如我們所愿,打印出來的值為true。貌似Canvas的事件監聽就這么簡單的解決了,不過事情真有這么簡單嗎。顯然是不可能的!我們再來舉個例子,這時候有兩個區域,并且我們需要分別給其綁定不同的事件:
const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d") ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10, 50) ctx.lineTo(50, 50) ctx.lineTo(50, 10) ctx.fillStyle= "black" ctx.fill() ctx.closePath() ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo(150, 100) ctx.fillStyle= "red" ctx.fill() ctx.closePath() canvas.addEventListener("click", function (e) { const canvasInfo = canvas.getBoundingClientRect() console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top)) })
這個時候,結果就不再如同我們所預計的一樣,當點擊其中黑色區域時,打印的值為false,點擊紅色區域時,打印的值為true。
其實原因很簡單,因為上述代碼,我們實際創建了兩個Path,而isPointInPath方法實際只檢測當前點是否處于最后一個Path當中,而例子中紅色區域為最后一個Path,所以只有點擊紅色區域時,isPointInPath方法才能判斷為true。現在我們改造一下代碼:
const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d") let drawArray = [] function draw1 () { ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10, 50) ctx.lineTo(50, 50) ctx.lineTo(50, 10) ctx.fillStyle= "black" ctx.fill() } function draw2 () { ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo(150, 100) ctx.fillStyle= "red" ctx.fill() ctx.closePath() } drawArray.push(draw1, draw2) drawArray.forEach(it => { it() }) canvas.addEventListener("click", function (e) { ctx.clearRect(0, 0, 400, 750) const canvasInfo = canvas.getBoundingClientRect() drawArray.forEach(it => { it() console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top)) }) })
上面的代碼我們進行了一個很大的改造,我們將每個Path放入到一個多帶帶的函數當中,并將它們push到一個數組當中。當觸發點擊事件時,我們清空Canvas,并遍歷數組重新繪制,每當繪制一個Path進行一次判斷,從而在調用isPointInPath方法時,我們能實時的獲取當前的最后一個Path,進而判斷出當前點所處的Path當中。
現在我們已經間接的實現了對每個Path的多帶帶事件監聽,可是其實現的方式需要一次又一次的重繪,那么有辦法不需要重繪就能監聽事件嗎?
首先我們需要知道一次又一次重繪的原因是因為isPointInPath方法是監聽的最后一個Path,不過我們在介紹這個方法的時候,說過其第一個參數是一個Path對象,當我們傳遞了這個參數后,Path就不再去取最后一個Path而是使用我們傳遞進去的這個Path,現在我們來個demo來驗證其可行性:
const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d") const path1 = new Path2D(); path1.rect(10, 10, 100,100); ctx.fill(path1) const path2 = new Path2D(); path2.moveTo(220, 60); path2.arc(170, 60, 50, 0, 2 * Math.PI); ctx.stroke(path2) canvas.addEventListener("click", function (e) { console.log(ctx.isPointInPath(path1, e.clientX, e.clientY)) console.log(ctx.isPointInPath(path2, e.clientX, e.clientY)) })
如上圖所示,我們點擊了左邊圖形,打印true,false;點擊右邊圖形,打印false,true。打印的結果表明是沒有問題的,不過由于其兼容性還有待加強,所以目前建議還是使用重繪方式來監聽事件。
結語Canvas的事件監聽講到這里基本就差不多了,原理很簡單,大家應該都能掌握。
github地址,歡迎start
自己寫的一個demo
const canvas = document.getElementById("canvas") class rectangular { constructor ( ctx, { top = 0, left = 0, width = 30, height = 50, background = "red" } ) { this.ctx = ctx this.top = top this.left = left this.width = width this.height = height this.background = background } painting () { this.ctx.beginPath() this.ctx.moveTo(this.left, this.top) this.ctx.lineTo(this.left + this.width, this.top) this.ctx.lineTo(this.left + this.width, this.top + this.height) this.ctx.lineTo(this.left, this.top + this.height) this.ctx.fillStyle = this.background this.ctx.fill() this.ctx.closePath() } adjust (left, top) { this.left += left this.top += top } } class circle { constructor ( ctx, { center = [], radius = 10, background = "blue" } ) { this.ctx = ctx this.center = [center[0] === undefined ? radius : center[0], center[1] === undefined ? radius : center[1]] this.radius = radius this.background = background } painting () { this.ctx.beginPath() this.ctx.arc(this.center[0], this.center[1], this.radius, 0, Math.PI * 2, false) this.ctx.fillStyle = this.background this.ctx.fill() this.ctx.closePath() } adjust (left, top) { this.center[0] += left this.center[1] += top } } class demo { constructor (canvas) { this.canvasInfo = canvas.getBoundingClientRect() this.renderList = [] this.ctx = canvas.getContext("2d") this.canvas = canvas this.rectangular = (config) => { let target = new rectangular(this.ctx, {...config}) this.addRenderList(target) return this } this.circle = (config) => { let target = new circle(this.ctx, {...config}) this.addRenderList(target) return this } this.addEvent() } addRenderList (target) { this.renderList.push(target) } itemToLast (index) { const lastItem = this.renderList.splice(index, 1)[0] this.renderList.push(lastItem) } painting () { this.ctx.clearRect(0, 0, this.canvasInfo.width, this.canvasInfo.height) this.renderList.forEach(it => it.painting()) } addEvent () { const that = this let startX, startY canvas.addEventListener("mousedown", e => { startX = e.clientX startY = e.clientY let choosedIndex = null this.renderList.forEach((it, index) => { it.painting() if (this.ctx.isPointInPath(startX, startY)) { choosedIndex = index } }) if (choosedIndex !== null) { this.itemToLast(choosedIndex) } document.addEventListener("mousemove", mousemoveEvent) document.addEventListener("mouseup", mouseupEvent) this.painting() }) function mousemoveEvent (e) { const target = that.renderList[that.renderList.length - 1] const currentX = e.clientX const currentY = e.clientY target.adjust(currentX - startX, currentY - startY) startX = currentX startY = currentY that.painting() } function mouseupEvent (e) { const target = that.renderList[that.renderList.length - 1] const currentX = e.clientX const currentY = e.clientY target.adjust(currentX - startX, currentY - startY) startX = currentX startY = currentY that.painting() document.removeEventListener("mousemove", mousemoveEvent) document.removeEventListener("mouseup", mouseupEvent) } } } const yes = new demo(canvas) .rectangular({}) .rectangular({top: 60, left: 60, background: "blue"}) .rectangular({top: 30, left: 20, background: "green"}) .circle() .circle({center: [100, 30], background: "red", radius: 5}) .painting()
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109686.html
摘要:那么既然有添加事件,就有移除事件,使用方式與添加事件幾乎完全一樣事件類型事件執行函數可選,為布爾值表示在冒泡捕獲階段執行唯一需要注意的是即移除事件的函數,這里只能寫函數名,而不能像添加事件一樣將整個功能函數全部寫入。 用戶交互也許是我們學習canvas動畫中首先需要掌握的部分。畢竟,如果沒有交互或者向動畫中做一些動態的輸入,那么這跟看電影有什么區別呢?用戶交互基于事件,一般來說包括:鼠...
摘要:那么既然有添加事件,就有移除事件,使用方式與添加事件幾乎完全一樣事件類型事件執行函數可選,為布爾值表示在冒泡捕獲階段執行唯一需要注意的是即移除事件的函數,這里只能寫函數名,而不能像添加事件一樣將整個功能函數全部寫入。 用戶交互也許是我們學習canvas動畫中首先需要掌握的部分。畢竟,如果沒有交互或者向動畫中做一些動態的輸入,那么這跟看電影有什么區別呢?用戶交互基于事件,一般來說包括:鼠...
閱讀 1626·2021-09-02 09:55
閱讀 1092·2019-08-30 13:19
閱讀 1393·2019-08-26 13:51
閱讀 1445·2019-08-26 13:49
閱讀 2372·2019-08-26 12:13
閱讀 452·2019-08-26 11:52
閱讀 1899·2019-08-26 10:58
閱讀 3084·2019-08-26 10:19