您好,登錄后才能下訂單哦!
這篇“html5中Canvas怎么添加事件”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“html5中Canvas怎么添加事件”文章吧。
一個(gè)傻瓜式的方式
我們都知道一個(gè)元素在觸發(fā)一個(gè)事件時(shí),其鼠標(biāo)的位置基本處于該元素之上,那么我們就自然而然的想到通過當(dāng)前鼠標(biāo)的位置以及物體所占據(jù)的位置進(jìn)行比對(duì),從而我們就能得出該物體是否應(yīng)觸發(fā)事件。這種方式比較簡(jiǎn)單,我就不用代碼演示了,不過既然我叫它傻瓜式的方式,很明顯它不是一個(gè)有效的解決方式。因?yàn)槲矬w所占據(jù)的位置并不一定是十分容易獲取,如果是矩形、圓形等我們還能通過一些簡(jiǎn)單的公式獲取其占據(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)是Path3D構(gòu)造函數(shù)new的對(duì)象。Path3D構(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
先來一個(gè)簡(jiǎn)單的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所占據(jù)的區(qū)域,黑色為我們實(shí)際添加事件的區(qū)域,在我們點(diǎn)擊黑色區(qū)域后,實(shí)際也的確如我們所愿,打印出來的值為true。貌似Canvas的事件監(jiān)聽就這么簡(jiǎn)單的解決了,不過事情真有這么簡(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))
})
這個(gè)時(shí)候,結(jié)果就不再如同我們所預(yù)計(jì)的一樣,當(dāng)點(diǎn)擊其中黑色區(qū)域時(shí),打印的值為false,點(diǎn)擊紅色區(qū)域時(shí),打印的值為true。
其實(shí)原因很簡(jiǎn)單,因?yàn)樯鲜龃a,我們實(shí)際創(chuàng)建了兩個(gè)Path,而isPointInPath方法實(shí)際只檢測(cè)當(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 path2 = new Path3D();
path2.rect(10, 10, 100,100);
ctx.fill(path2)
const path3 = new Path3D();
path3.moveTo(220, 60);
path3.arc(170, 60, 50, 0, 2 * Math.PI);
ctx.stroke(path3)
canvas.addEventListener('click', function (e) {
console.log(ctx.isPointInPath(path2, e.clientX, e.clientY))
console.log(ctx.isPointInPath(path3, e.clientX, e.clientY))
})
我們點(diǎn)擊了左邊圖形,打印true,false;點(diǎn)擊右邊圖形,打印false,true。打印的結(jié)果表明是沒有問題的,不過由于其兼容性還有待加強(qiáng),所以目前建議還是使用重繪方式來監(jiān)聽事件。
結(jié)語
Canvas的事件監(jiān)聽講到這里基本就差不多了,原理很簡(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()
以上就是關(guān)于“html5中Canvas怎么添加事件”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。