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

首頁 > 編程 > HTML > 正文

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

2024-08-26 00:21:28
字體:
供稿:網(wǎng)友

作為一個(gè)前端,給元素添加事件是一件司空見慣的事情??墒窃贑anvas中,其所畫的任何東西都是無法獲取的,更別說添加事件,那么我們對(duì)其就束手無策了嗎?當(dāng)然不是的!我們?cè)谄綍r(shí)項(xiàng)目中肯定都用過許多Canvas的框架,我們發(fā)現(xiàn)事件在這些框架中已經(jīng)使用的十分成熟了,而且并沒有出現(xiàn)特別嚴(yán)重的問題。那么我們可以肯定的是,事件在Canvas中并不是一個(gè)無法觸及的事情。

一個(gè)傻瓜式的方式

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

一個(gè)較聰明的方式

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

介紹isPointInPath

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

isPointInPath的入?yún)⒊鰠ⅲ篶tx.isPointInPath([path, ]x, y [, fillRule]),該方法的參數(shù)有4個(gè),其中path和fillRule為選填,x和y為必填。我們依次介紹4個(gè)參數(shù)。

path:看到這個(gè)參數(shù),我開始以為是beginPath或者closePath的返回值,很可惜的是這兩個(gè)方法并沒有返回值,在查閱了資料后,發(fā)現(xiàn)是Path2D構(gòu)造函數(shù)new的對(duì)象。Path2D構(gòu)造函數(shù)具體用法。不過可惜的是該方法可能由于兼容性的問題,目前看了一些開源框架都還未使用。

x,y:這兩個(gè)參數(shù)很好理解,就是x軸和y軸的距離,需要注意的是,其相對(duì)位置是Canvas的左上角。

fillRule:nonzero(默認(rèn)),evenodd。非零環(huán)繞規(guī)則和奇偶規(guī)則是圖形學(xué)中判斷一個(gè)點(diǎn)是否處于多邊形內(nèi)的規(guī)則,其中非零環(huán)繞規(guī)則是Canvas的默認(rèn)規(guī)則。想具體了解這兩種規(guī)則的,可以自己去查閱資料,這里就不增加篇幅介紹了。

上面介紹完了入?yún)?,那么isPointInPath方法的出參想必大家都可以猜到了,就是true和false。

使用isPointInPath

上一節(jié)介紹完isPointInPath方法后,我們現(xiàn)在就來使用它吧。

先來一個(gè)簡單的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所占據(jù)的區(qū)域,黑色為我們實(shí)際添加事件的區(qū)域,在我們點(diǎn)擊黑色區(qū)域后,實(shí)際也的確如我們所愿,打印出來的值為true。貌似Canvas的事件監(jiān)聽就這么簡單的解決了,不過事情真有這么簡單嗎。顯然是不可能的!我們?cè)賮砼e個(gè)例子,這時(shí)候有兩個(gè)區(qū)域,并且我們需要分別給其綁定不同的事件:

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

這個(gè)時(shí)候,結(jié)果就不再如同我們所預(yù)計(jì)的一樣,當(dāng)點(diǎn)擊其中黑色區(qū)域時(shí),打印的值為false,點(diǎn)擊紅色區(qū)域時(shí),打印的值為true。

其實(shí)原因很簡單,因?yàn)樯鲜龃a,我們實(shí)際創(chuàng)建了兩個(gè)Path,而isPointInPath方法實(shí)際只檢測當(dāng)前點(diǎn)是否處于最后一個(gè)Path當(dāng)中,而例子中紅色區(qū)域?yàn)樽詈笠粋€(gè)Path,所以只有點(diǎn)擊紅色區(qū)域時(shí),isPointInPath方法才能判斷為true?,F(xiàn)在我們改造一下代碼:

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

上面的代碼我們進(jìn)行了一個(gè)很大的改造,我們將每個(gè)Path放入到一個(gè)單獨(dú)的函數(shù)當(dāng)中,并將它們push到一個(gè)數(shù)組當(dāng)中。當(dāng)觸發(fā)點(diǎn)擊事件時(shí),我們清空Canvas,并遍歷數(shù)組重新繪制,每當(dāng)繪制一個(gè)Path進(jìn)行一次判斷,從而在調(diào)用isPointInPath方法時(shí),我們能實(shí)時(shí)的獲取當(dāng)前的最后一個(gè)Path,進(jìn)而判斷出當(dāng)前點(diǎn)所處的Path當(dāng)中。

現(xiàn)在我們已經(jīng)間接的實(shí)現(xiàn)了對(duì)每個(gè)Path的單獨(dú)事件監(jiān)聽,可是其實(shí)現(xiàn)的方式需要一次又一次的重繪,那么有辦法不需要重繪就能監(jiān)聽事件嗎?

首先我們需要知道一次又一次重繪的原因是因?yàn)閕sPointInPath方法是監(jiān)聽的最后一個(gè)Path,不過我們?cè)诮榻B這個(gè)方法的時(shí)候,說過其第一個(gè)參數(shù)是一個(gè)Path對(duì)象,當(dāng)我們傳遞了這個(gè)參數(shù)后,Path就不再去取最后一個(gè)Path而是使用我們傳遞進(jìn)去的這個(gè)Path,現(xiàn)在我們來個(gè)demo來驗(yàn)證其可行性:

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

如上圖所示,我們點(diǎn)擊了左邊圖形,打印true,false;點(diǎn)擊右邊圖形,打印false,true。打印的結(jié)果表明是沒有問題的,不過由于其兼容性還有待加強(qiáng),所以目前建議還是使用重繪方式來監(jiān)聽事件。

結(jié)語

Canvas的事件監(jiān)聽講到這里基本就差不多了,原理很簡單,大家應(yīng)該都能掌握。
github地址,歡迎start

附錄

自己寫的一個(gè)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,添加事件

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VeVb武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到HTML教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
91成年人视频| 天天综合网在线观看| 日本裸体美女视频| 丁香啪啪综合成人亚洲| 色av成人天堂桃色av| 亚洲视频在线视频| 翔田千里在线视频| 欧美日韩大陆一区二区| 亚洲自拍偷拍综合| 亚洲精品国久久99热| 国产日产欧美视频| 中文在线综合| 精品少妇无遮挡毛片| xxxxx69·hd| 亚洲日本免费| 手机在线免费看毛片| 在线a免费看| 在线观看视频免费| 国产在线观看精品一区二区三区| 亚洲va男人天堂| 久久久久九九精品影院| 久久夜色精品亚洲噜噜国产mv| 国产性猛交普通话对白| 人妻丰满熟妇av无码区| 污片在线观看一区二区| 91视频成人免费| 免费激情网址| 欧美男女性生活在线直播观看| 天天摸夜夜操| 免费网站观看www在线观| 久久久久久久久久久久网站| 国产精品女主播一区二区三区| av免费在线免费| 欧美亚洲丝袜| 亚洲欧洲在线观看| 国产欧美日韩免费看aⅴ视频| 欧美精品久久一区二区三区| 欧美bbbbb性bbbbb视频| 精品露脸国产偷人在视频| 成人av资源在线播放| 亚洲自拍偷拍二区| 日韩欧美国产免费| 亚洲japanese制服美女| 色综合视频网站| 99九九精品视频| 18啪啪污污免费网站| 51亚洲精品| 最近97中文超碰在线| 在线看的毛片| 亚洲欧美精品suv| 亚洲第一页在线视频| 男裸体无遮挡网站| 国产欧美亚洲精品| 1024亚洲合集| 日日夜夜精品视频免费观看| 青青草成人激情在线| 国产精品v欧美精品v日本精品动漫| 国产在线98福利播放视频| 日韩网站免费观看| 伊人狠狠色丁香综合尤物| 色88888久久久久久影院| 岛国片免费看| 日韩欧美一区二区视频在线播放| 高清中文字幕一区二区三区| 国产成人综合一区| 日本丶国产丶欧美色综合| 国产小视频一区| 日韩高清不卡一区| 国产91在线免费| 日韩美女福利视频| 99国产成人精品| 苍井空张开腿实干12次| 神马久久资源| 国产精选一区二区| 国产成人av片| 亚洲综合激情另类小说区| 亚洲国产裸拍裸体视频在线观看乱了中文| 欧美aaa大片| 日本中文字幕一级片| 石原莉奈一区二区三区高清在线| 国产欧美一区二区精品秋霞影院| 麻豆tv在线播放| 日韩av中文字幕在线免费观看| 精品欧美乱码久久久久久| 久久久国产成人| 无人日本免费视频| 亚洲精品日韩综合观看成人91| 国产+高潮+白浆+无码| 性欧美办公室18xxxxhd| 日韩精品一二三区| 欧美午夜视频一区二区| 国产日韩欧美一区二区三区乱码| 久久国产生活片100| 麻豆一区一区三区四区| 久久99国产精品| 欧美一区二区三区四区高清| 亚洲欧美激情在线视频| 91麻豆精品91久久久久同性| 波多野结衣网站| 成人精品久久| 欧美日韩国产小视频| free性护士videos欧美| 黄网站app在线观看下载视频大全官网| 精品国产乱码久久久久久久软件| 一本色道久久综合狠狠躁篇的优点| 内射无码专区久久亚洲| 人妻无码一区二区三区| 欧美电影一区二区| 精品国产91久久久| 精品午夜福利视频| 日本三级在线电影| 天天摸天天操天天干| jizzjizzjizz欧美| 香蕉乱码成人久久天堂爱免费| jizz中文字幕| 亚洲人精品午夜在线观看| 国产v亚洲v天堂无码久久久| 国产视频aaa| 99在线播放| 欧美午夜丰满在线18影院| 日韩大片免费在线观看| 国产精品三级美女白浆呻吟| ww亚洲ww在线观看国产| 污视频网站免费观看| 日韩成年人视频| 成人丁香基地| h网站在线看| 欧美日韩国产一区二区三区不卡| 日本免费在线一区| 成人精品免费在线观看| 日韩免费在线看| 九色蝌蚪自拍| 欧美日韩综合视频| 国产精品海角社区在线观看| 精品一区二区久久久| 五月婷婷六月色| 亚洲蜜桃精久久久久久久久久久久| www.色.com| 亚洲色在线视频| 日韩一区二区在线观看视频播放| 一区二区三区不卡在线观看| 国产亚洲污的网站| 国产精品亚洲专一区二区三区| 日本怡春院一区二区| 亚洲精品在线观看www| 一道精品视频一区二区三区图片| 波多野结衣在线播放一区| 欧美激情一级二级三级在线视频| 国产精品福利影院| 中文字幕免费一区二区| 日本免费一区二区视频| 国产在线精品免费av| 久久精品午夜一区二区福利| 亚洲精品无码专区在线播放| 国产美女视频免费观看下载软件| 日本成人在线不卡| 久久国产精品色av免费看| 日本高清一二三区| 亚洲一区色图| 亚洲美女av在线播放| 91黄色免费看| 色综合 综合色| 亚洲精品国产精品国自产网站按摩| 精品免费在线| 成年大片免费视频播放二级| 无人在线观看的免费高清视频| 亚洲国产成人精品一区二区三区| 日韩欧美一级| 国产高清精品软男同| 欧美日韩国产免费一区二区三区| 精品国产免费人成在线观看| 宅男噜噜噜66国产日韩在线观看| 久草福利在线观看| 中文在线资源在线| 久久精品视频导航| 欧美日韩午夜电影网| 青青草原成人在线视频| 日韩视频在线观看免费| 亚洲午夜私人影院| 一个人www视频在线免费观看| 国产欧美一区二区三区视频| 日韩精品在线第一页| 亚洲综合国产精品| 国产精品一区高清| 日韩欧美第二区在线观看| 日韩有码在线电影| 欧美一区二区三区电影在线观看| av污在线观看| gogo高清在线播放免费| 国产传媒av在线| 国产午夜亚洲精品羞羞网站| 天天色天天射综合网| 人妻偷人精品一区二区三区| 久久久久亚洲av成人无码电影| 日日躁夜夜躁aaaabbbb| 久久久午夜视频| 亚洲日本视频在线观看| 粉色视频免费看| 综合激情成人伊人| 美乳少妇欧美精品| 亚洲第一视频区| 亚洲色图激情小说| 亚洲精品乱码久久久久久自慰| 欧美三级中文字幕| 免费国产视频| 日韩在线观看电影完整版高清免费| 中文字幕一区二区三区四区| 一级黄色小视频| 国产日韩一区二区三免费高清| 免费在线观看日韩欧美| 久久久久久一区| 国产精品久久久精品| 国产美女极度色诱视频www| 中文字幕视频在线播放| 中文字幕在线日亚洲9| 国产亚洲色婷婷久久99精品91| 亚洲国产成人精品女人| 在线播放日韩专区| 色先锋影音av| 国产美女高潮一区二区三区| 男女视频免费网站| 中文字幕永久在线观看| 日韩在线观看免费全| 国产精品欧美久久久久无广告| 精品福利网址导航| 国产乱码精品一区二三赶尸艳谈| 欧美另类z0zx974| 小小的日本在线观看免费色网| 欧美日韩不卡在线| 看高清中日韩色视频| 伊人av免费在线观看| 亚洲深深色噜噜狠狠爱网站| 国产一区二区三区高清播放| 在线观看无遮挡| 久久99蜜桃精品| 欧美xxxx吸乳| 成人av资源在线观看| 日韩一区二区av| 26uuu亚洲电影在线观看| 亚洲一区二区黄| 免费一区视频| 亚洲一级二级三级在线免费观看| 亚洲免费网站观看视频| 精品人妻无码一区二区三区换脸| 亚洲а∨天堂久久精品9966| 香蕉视频999| 洋洋成人永久网站入口| 国产又白又嫩又爽又黄| 久久久久久国产精品免费播放| 亚洲性生活视频| 日日夜夜亚洲精品| 嗯啊主人调教在线播放视频| 在线观看视频黄色| 1stkiss在线漫画| 欧美福利视频在线| 午夜福利视频一区二区| av在线播放中文字幕| 国产亚洲色婷婷久久99精品| 伊人成人网在线看| 美女久久久久| 久久国产精品二区| 国产精品欧美一区二区三区奶水| 日韩综合一区二区三区| 日韩成人黄色av| 亚洲精品成a人ⅴ香蕉片| 久久影视三级福利片| 国产成人亚洲综合青青| 国产精品初高中精品久久| 情趣视频网站在线免费观看| 手机在线成人免费视频| 色丁香久综合在线久综合在线观看| 亚洲欧美激情精品一区二区| 亚洲一区电影在线观看| 精品中文字幕一区二区三区av| 亚洲一区二区三区在线观看视频| 天天爽夜夜爽夜夜爽精品视频| 亚洲一二三区在线观看| 六月婷婷一区| 国产精品99久久久久久久女警| 丰满少妇高潮久久三区| 免费涩涩18网站入口| 国产91对白在线观看九色| 男女视频网站免费观看| 欧美aa一级| 日日橹狠狠爱欧美超碰| xxxxaaa欧美另类| www.国产精| 国产精品69久久久久水密桃| 免费成人高清在线视频theav| 拍拍拍无挡免费播放视频在线观看| 在线观看日韩毛片| 国产一级特黄a大片免费| 久久精品国产99久久| 亚洲一区日韩精品| 懂色av中文字幕| 国产精品久久久久久久久晋中| 中文字幕精品影院| 污污污污污污www网站免费| 福利片一区二区| 亚洲欧美一区二区三区久本道91| 久久精品无码一区二区三区| 福利在线免费视频| 激情五月综合| 韩剧1988在线观看免费完整版| 亚洲第一页在线观看| 人妻无码中文字幕免费视频蜜桃| 久久男人av资源网站| 国产91精品久久久| 欧美精选在线| 久久aⅴ乱码一区二区三区| 高清视频在线观看三级| 91精产国品一二三| xxxx性bbbb欧美野外| 中文字幕成人| 精品人妻伦一区二区三区久久| 欧美日韩一区二区在线播放| bt天堂新版中文在线地址| 欧美色图另类小说| 国产黄色一区二区| 黑人40厘米全进去| 国产精品第二十页| 欧美日韩在线中文| 亚洲一区二区三区精品中文字幕| 国内精品国产三级国产99| 91网站免费| 国产视频九色蝌蚪| 日韩激情一区| bdsm在线观看播放视频|