亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > HTML > 正文

如何在Canvas中添加事件的方法示例

2024-08-26 00:21:28
字體:
來源:轉載
供稿:網友

作為一個前端,給元素添加事件是一件司空見慣的事情??墒窃贑anvas中,其所畫的任何東西都是無法獲取的,更別說添加事件,那么我們對其就束手無策了嗎?當然不是的!我們在平時項目中肯定都用過許多Canvas的框架,我們發現事件在這些框架中已經使用的十分成熟了,而且并沒有出現特別嚴重的問題。那么我們可以肯定的是,事件在Canvas中并不是一個無法觸及的事情。

一個傻瓜式的方式

我們都知道一個元素在觸發一個事件時,其鼠標的位置基本處于該元素之上,那么我們就自然而然的想到通過當前鼠標的位置以及物體所占據的位置進行比對,從而我們就能得出該物體是否應觸發事件。這種方式比較簡單,我就不用代碼演示了,不過既然我叫它傻瓜式的方式,很明顯它不是一個有效的解決方式。因為物體所占據的位置并不一定是十分容易獲取,如果是矩形、圓形等我們還能通過一些簡單的公式獲取其占據的位置,可是在復雜點的多邊形,甚至是多邊形的某些邊是弧線的,顯而易見,我們這時候再獲取其所占據的位置時是一件極其復雜且難度極大的事情,所以這種方式只適合自己在做一些demo中使用,并不適用于大多數的情況。

一個較聰明的方式

既然上面這種方式碰壁了,那么我們只能另辟蹊徑。在翻閱CanvasAPI的時候,找到了一個方法isPointInPath,貌似正是我們苦苦尋找的良藥。

介紹isPointInPath

isPointInPath的作用:顧名思義,我們很直觀的可以知道該方法用以判斷點是否處于路徑當中。

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,添加事件

如圖所示,灰色部分為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))  })

Canvas,添加事件

這個時候,結果就不再如同我們所預計的一樣,當點擊其中黑色區域時,打印的值為false,點擊紅色區域時,打印的值為true。

其實原因很簡單,因為上述代碼,我們實際創建了兩個Path,而isPointInPath方法實際只檢測當前點是否處于最后一個Path當中,而例子中紅色區域為最后一個Path,所以只有點擊紅色區域時,isPointInPath方法才能判斷為true?,F在我們改造一下代碼:

  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))  })

Canvas,添加事件

如上圖所示,我們點擊了左邊圖形,打印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()

Canvas,添加事件

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到HTML教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲电影免费观看高清| 亚洲欧美变态国产另类| 色综合伊人色综合网| 久久久久久久亚洲精品| 精品国产乱码久久久久酒店| 麻豆精品精华液| 亚洲成人av在线播放| 日韩免费在线观看视频| 成人黄色免费看| 国产欧美一区二区三区久久人妖| 青青久久av北条麻妃海外网| 亚洲成人精品久久久| 91久久久亚洲精品| 日韩美女毛茸茸| 日韩在线视频线视频免费网站| 日韩精品一区二区视频| 3344国产精品免费看| 国产亚洲欧美aaaa| 欧美日韩另类视频| 亚洲欧美一区二区三区情侣bbw| 夜夜躁日日躁狠狠久久88av| 欧美黄色小视频| 亚洲精品网址在线观看| 久久成人免费视频| 国产日韩中文字幕| 国产精品免费视频久久久| 国产日韩在线观看av| 日韩精品在线观| 午夜精品一区二区三区在线视频| 97国产精品人人爽人人做| 欧美性videos高清精品| 在线亚洲男人天堂| 久久久国产精品一区| 亚洲国产欧美日韩精品| 丝袜亚洲欧美日韩综合| 久久伊人精品一区二区三区| 久久av红桃一区二区小说| 国产精品久久久久aaaa九色| 精品国产拍在线观看| 国产精品嫩草视频| 久久久女女女女999久久| 国产精品一区二区久久久久| 在线播放日韩专区| 91热精品视频| 亚洲风情亚aⅴ在线发布| 午夜精品一区二区三区视频免费看| 国产精品美女999| 日韩中文字幕久久| 国产97在线播放| 久久精品久久久久久国产 免费| 国产精品a久久久久久| 日韩中文字幕网站| 亚洲欧美日韩高清| 亚洲成人性视频| 日韩欧美在线中文字幕| 日韩视频免费大全中文字幕| 亚洲成人av中文字幕| 97视频在线观看网址| 国产精品久久久久久av福利| 黑人巨大精品欧美一区二区免费| 亚洲精品在线看| 成人免费淫片视频软件| 久久久精品免费视频| 中日韩午夜理伦电影免费| 国产精品欧美激情在线播放| 欧美亚州一区二区三区| 最近2019免费中文字幕视频三| 精品在线观看国产| 国产一区二区三区在线视频| 亚洲精品视频在线播放| 欧美黑人性视频| 国产综合色香蕉精品| 在线精品国产欧美| 欧美极品少妇全裸体| 亚洲国产小视频在线观看| 日本成人在线视频网址| 亚洲free性xxxx护士hd| 日韩电视剧在线观看免费网站| 欧美黑人极品猛少妇色xxxxx| 久久免费视频这里只有精品| 美女扒开尿口让男人操亚洲视频网站| 欧美日韩国产色| xxxx欧美18另类的高清| 亚洲精品欧美日韩专区| 日韩免费不卡av| 欧美裸体xxxx极品少妇| 日韩在线观看视频免费| 亚洲国产精品久久| 亚洲激情在线观看| 一区二区欧美久久| 欧美色视频日本高清在线观看| 亚洲伊人久久大香线蕉av| 国产精品三级美女白浆呻吟| 国产成人精品视频在线观看| 精品国产一区二区三区久久久狼| 97超碰色婷婷| 日韩av不卡在线| 日韩高清电影免费观看完整| 日韩av一区在线| 国产97在线|亚洲| 69**夜色精品国产69乱| 国产精品va在线播放我和闺蜜| 亚洲欧美日韩图片| 最新国产精品亚洲| 亚洲丁香婷深爱综合| 久久综合伊人77777| 日韩欧美一区二区三区久久| 91精品视频在线| 国语自产在线不卡| 久久久这里只有精品视频| 亚洲网站在线看| 精品久久久久久久中文字幕| 国内精品视频久久| 色中色综合影院手机版在线观看| 欧美成人黑人xx视频免费观看| 欧美综合在线第二页| 国产精品一久久香蕉国产线看观看| 青青草国产精品一区二区| 久久久国产精品免费| 久久99精品久久久久久噜噜| 伊是香蕉大人久久| 国产亚洲精品日韩| 精品露脸国产偷人在视频| 日韩av片免费在线观看| 亚洲第一男人av| 一区二区在线免费视频| 欧美亚洲另类激情另类| 68精品久久久久久欧美| 岛国av一区二区在线在线观看| 欧美最猛性xxxxx免费| 亚洲国产成人精品电影| 欧美国产欧美亚洲国产日韩mv天天看完整| 91av在线免费观看| 久久久这里只有精品视频| 国产精品嫩草影院一区二区| 国产精品视频一区国模私拍| 97色在线视频观看| 欧美在线一级视频| 欧美久久精品午夜青青大伊人| 亚洲在线视频福利| 亚洲免费人成在线视频观看| 97av在线播放| 久久精品影视伊人网| 亚洲美女在线视频| 中文字幕一区二区三区电影| 精品国产31久久久久久| 国产亚洲精品日韩| 国产亚洲精品久久久久久牛牛| 亚洲一级黄色av| 国产亚洲欧洲高清一区| 91欧美精品成人综合在线观看| 欧美日韩国产成人在线| 久久五月天综合| 日韩精品免费一线在线观看| 欧美成人剧情片在线观看| 欧美精品videosex极品1| 亚洲国产欧美在线成人app| 国产精品偷伦视频免费观看国产| www.欧美免费| 国产精品美女午夜av| 91网站在线免费观看| 成人97在线观看视频| 亚洲精品福利在线观看| 久久99国产综合精品女同|