溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件

發(fā)布時(shí)間:2021-05-18 16:22:11 來(lái)源:億速云 閱讀:169 作者:Leah 欄目:web開(kāi)發(fā)

如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

像素法

像素檢測(cè)法的思路是,將canvas中的多個(gè)圖形(如果有多個(gè)的話)分別離屏繪制,并用 getImageData() 方法分別獲取到像素?cái)?shù)據(jù)保存起來(lái)。當(dāng)canvas元素監(jiān)聽(tīng)到點(diǎn)擊事件時(shí),通過(guò)點(diǎn)擊坐標(biāo)可以直接推算出點(diǎn)擊發(fā)生在canvas上的第幾個(gè)像素,然后遍歷前面保存的圖形數(shù)據(jù),看看這個(gè)像素的alpha值是不是0,如果是0說(shuō)明落點(diǎn)不在當(dāng)前圖形內(nèi),否則就說(shuō)明點(diǎn)到了這個(gè)圖形。

根據(jù)點(diǎn)擊坐標(biāo)得到所點(diǎn)擊的像素序號(hào)的方法:

像素序號(hào) = (縱坐標(biāo)-1) * canvas寬度 + 橫坐標(biāo)

比如在寬度為 5 的畫(huà)布上點(diǎn)擊坐標(biāo) (3,3) ,根據(jù)上述公式得到像素序號(hào)是 (3-1) * 5 + 3 = 18 ,如圖所示:

如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件

因?yàn)閏anvas導(dǎo)出的圖形數(shù)據(jù)是將每個(gè)像素以 rgba 的順序存成4個(gè)數(shù)字組成的數(shù)組,所以想訪問(wèn)指定像素的alpha值,只要讀取這個(gè)數(shù)組的第 pIndex * 4 + 3 個(gè)值就可以了,如果這個(gè)值不為0,說(shuō)明該像素可見(jiàn),也就是點(diǎn)擊到了該圖形。

這個(gè)方法是我認(rèn)為思路最直接、結(jié)果最準(zhǔn)確、而且對(duì)圖形形狀沒(méi)有任何要求的方法,但這個(gè)方法有一個(gè)致命的局限,當(dāng)圖形需要在畫(huà)布上移動(dòng)時(shí),要頻繁的創(chuàng)建數(shù)據(jù)緩存才能保證檢測(cè)結(jié)果準(zhǔn)確,受到畫(huà)布尺寸和圖形數(shù)量的影響, getImageData() 方法的性能會(huì)成為嚴(yán)重的瓶頸。所以如果canvas圖形是靜態(tài)的,這個(gè)方法非常適合,否則就不適合用這個(gè)方法了。

角度法

角度判斷法的原理很容易理解,如果一個(gè)點(diǎn)在多邊形內(nèi)部,則該點(diǎn)與多邊形所有頂點(diǎn)兩兩構(gòu)成的夾角,相加應(yīng)該剛好等于360°。

如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件

計(jì)算過(guò)程可以轉(zhuǎn)變?yōu)橐韵氯齻€(gè)步驟:

1.已知多邊形頂點(diǎn)和已知坐標(biāo),將坐標(biāo)與頂點(diǎn)兩兩組合成三點(diǎn)隊(duì)列
2. 已知三點(diǎn)求夾角,可以使用 余玄定理
3.判斷夾角之和是否360°

每一步都很簡(jiǎn)單,實(shí)現(xiàn)如下:

//計(jì)算兩點(diǎn)距離
const getDistence = function (p1, p2) {
  return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y))
};
//角度法判斷點(diǎn)在多邊形內(nèi)部
const checkPointInPolyline = (point, polylinePoints) => {
    let totalA = 0;
    const A = point;
    for (let i = 0; i < polylinePoints.length; i++) {
        let B, C;
        if (i === polylinePoints.length - 1) {
            B = {
                x: polylinePoints[i][0],
                y: polylinePoints[i][1]
            };
            C = {
                x: polylinePoints[0][0],
                y: polylinePoints[0][1]
            };
        } else {
            B = {
                x: polylinePoints[i][0],
                y: polylinePoints[i][1]
            };
            C = {
                x: polylinePoints[i + 1][0],
                y: polylinePoints[i + 1][1]
            };
        }
        //計(jì)算角度
        const angleA = Math.acos((Math.pow(getDistence(A, C), 2) + Math.pow(getDistence(A, B), 2) - Math.pow(getDistence(B, C), 2)) / (2 * getDistence(A, C) * getDistence(A, B)))
        totalA += angleA
    }
    //判斷角度之和
    return totalA === 2 * Math.PI
}

這個(gè)方法有一個(gè)局限性,就是圖形必須是 凸多邊形 。如果不是凸多邊形需要先切割成凸多邊形再計(jì)算,這就比較復(fù)雜了。

類似的思路還有面積法,如果一個(gè)點(diǎn)在多邊形內(nèi)部,那么該點(diǎn)與多邊形所有頂點(diǎn)兩兩構(gòu)成的三角形,面積相加應(yīng)該等于多邊形的面積,首先計(jì)算多邊形的面積就很麻煩,所以這種方法可以直接pass掉。

射線法

射線法是一個(gè)我講不清道理但非常好用的方法,只要判斷點(diǎn)與多邊形一側(cè)的交點(diǎn)個(gè)數(shù)為奇數(shù),則點(diǎn)在多邊形內(nèi)部。需要注意的是,只要數(shù)任何一側(cè)的焦點(diǎn)個(gè)數(shù)就可以,比如左側(cè)。這個(gè)方法不限制多邊形的類型,凸多邊形、凹多邊形甚至環(huán)形都可以。

如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件

實(shí)現(xiàn)起來(lái)也非常簡(jiǎn)單:


 

const checkPointInPolyline = (point, polylinePoints) => {
    //射線法
  let leftSide = 0;
  const A = point;
  for (let i = 0; i < polylinePoints.length; i++) {
    let B, C;
    if (i === polylinePoints.length - 1) {
      B = {
        x: polylinePoints[i][0],
        y: polylinePoints[i][1]
      };
      C = {
        x: polylinePoints[0][0],
        y: polylinePoints[0][1]
      };
    } else {
      B = {
        x: polylinePoints[i][0],
        y: polylinePoints[i][1]
      };
      C = {
        x: polylinePoints[i + 1][0],
        y: polylinePoints[i + 1][1]
      };
    }
    //判斷左側(cè)相交
    let sortByY = [B.y, C.y].sort((a,b) => a-b)
    if (sortByY[0] < A.y && sortByY[1] > A.y){
      if(B.x<A.x || C.x < A.x){
        leftSide++
      }
    }
  }
  return leftSide % 2 === 1
}

看完上述內(nèi)容,你們掌握如何在html5中監(jiān)聽(tīng)canvas內(nèi)部元素點(diǎn)擊事件的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI