フロントエンドとして、要素にイベントを追加するのは一般的なことです。しかし、Canvas ではイベントの追加はおろか、そこに描かれたものは何も取得できないので、それについては無力なのでしょうか。もちろん違います!私たちは日々のプロジェクトで多くの Canvas フレームワークを使用していると思いますが、これらのフレームワークではイベントが非常に成熟して使用されており、特に深刻な問題は発生していません。したがって、確かなことは、Canvas ではイベントはアンタッチャブルなものではないということです。
愚かなアプローチ要素がイベントをトリガーするとき、マウスの位置は基本的に要素の上にあることは誰もが知っているので、オブジェクトがイベントをトリガーする必要があるかどうかを取得できるように、現在のマウスの位置とオブジェクトが占める位置を比較することを自然に考えることになります。イベント。この方法は比較的単純なので、コードを使って説明することはしませんが、私はこれを愚かな方法と呼んでいますので、効果的な解決策ではないことは明らかです。オブジェクトが占める位置は、必ずしも簡単に取得できるわけではないため、長方形や円などの場合は、いくつかの単純な公式を使用してその位置を取得することもできます。ただし、複雑な点や一部の辺を含む多角形の場合は、その位置を取得することもできます。ポリゴンは曲線です。明らかに、現時点でポリゴンが占める位置を取得するのは非常に複雑で困難です。そのため、この方法は一部のデモでの使用にのみ適しており、ほとんどの条件には適していません。
より賢い方法上記の方法は壁にぶつかったので、別の方法を見つけるしかありません。 CanvasAPI を参照していると、isPointInPath メソッドを見つけました。これが私たちが探している薬と思われます。
isPointInPath の紹介isPointInPath の役割: 名前が示すように、このメソッドがポイントがパス内にあるかどうかを判断するために使用されることが直感的にわかります。
isPointInPath の入力パラメータと出力パラメータは次のとおりです: ctx.isPointInPath([path, ]x, y [, fillRule]) このメソッドには 4 つのパラメータがあり、そのうちの path と fillRule はオプションであり、x と y は必須です。 4つのパラメータを順番に紹介します。
path: このパラメータを見たとき、最初は beginPath または closePath の戻り値だと思いましたが、残念ながら、これら 2 つのメソッドは値を返しませんでした。情報を確認したところ、それが新しい Path2D コンストラクターのオブジェクトであることがわかりました。 Path2D コンストラクターの具体的な使用法。ただし、この方法は互換性の問題が原因である可能性があり、現在、一部のオープンソース フレームワークは使用されていないのが残念です。
x、y: これら 2 つのパラメータは、x 軸と y 軸の間の距離であり、その相対位置が Canvas の左上隅であることに注意してください。
fillRule: 非ゼロ (デフォルト)、偶数。非ゼロ ラッピング ルールと奇数偶数ルールは、点がポリゴン内にあるかどうかを決定するためのグラフィックスにおけるルールです。非ゼロ ラッピング ルールは、Canvas のデフォルトのルールです。この2つのルールについて詳しく知りたい方はご自身で調べていただければと思いますので、ここでは紹介しません。
上記の入力パラメーターを導入した後は、おそらく誰もが isPointInPath メソッドの出力パラメーター (true と false) を推測できるでしょう。
isPointInPath の使用前のセクションで isPointInPath メソッドを紹介した後、それを使用してみましょう。
簡単なデモから始めましょう:
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= '黒' 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のイベント監視は簡単に解決できそうな気がしますが、本当にそんなに簡単なのでしょうか?明らかに不可能です!別の例を見てみましょう。現時点では 2 つの領域があり、それらに異なるイベントをバインドする必要があります。
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= '黒' ctx.fill() ctx.closePath() ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo(150, 100) ctx.fillStyle = '赤' 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 になります。
実際、理由は非常に単純です。上記のコードにより、実際には 2 つのパスが作成され、isPointInPath メソッドは実際には現在のポイントが最後のパスにあるかどうかのみを検出します。この例では、赤い領域が最後のパスです。したがって、赤い領域がクリックされた場合にのみ、isPointInPath メソッドは true と判断されます。次に、コードを変換しましょう。
const Canvas = document.getElementById('canvas') const ctx = Canvas.getContext('2d') letdrawArray = [] functiondraw1 () { ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10) 、50) ctx.lineTo(50, 50) ctx.lineTo(50, 10) ctx.fillStyle= 'black' ctx.fill() } functiondraw2 () { ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo (150, 100) ctx.fillStyle= '赤' 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)) }) })
上記のコードに大きな変更を加え、各パスを個別の関数に配置し、配列にプッシュしました。クリックイベントがトリガーされると、Canvas をクリアし、配列を走査して再描画し、Path が描画されるたびに判定を行うため、isPointInPath メソッドを呼び出すと、最新の現在の Path をリアルタイムで取得できます。パスの中で現在のポイントを判断します。
ここで、パスごとに個別のイベント監視を間接的に実装しましたが、その実装方法では何度も再描画する必要があります。では、再描画せずにイベントを監視する方法はあるのでしょうか。
まず、何度も再描画する理由は、isPointInPath メソッドが監視される最後の Path であるためであることを知っておく必要があります。ただし、このメソッドを導入したときに、その最初のパラメーターが Path オブジェクトであると述べました。このパラメータを渡すと、Path は最後の Path を使用せず、渡した Path を使用します。次に、デモを実行して、その実現可能性を確認しましょう。
const Canvas = document.getElementById('canvas') const ctx = Canvas.getContext('2d') const path1 = new Path2D(); ctx.fill(path1);新しいPath2D(); path2.moveTo(220, 60); 60, 50, 0, 2 * Math.PI); ctx.ストローク(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 が出力されます。印刷結果には問題がないことがわかりますが、互換性を改善する必要があるため、現時点では redraw メソッドを使用してイベントをリッスンすることを推奨します。
結論Canvas のイベント監視については基本的にここで説明します。原理は非常に簡単なので、誰でも習得できるはずです。
github アドレス、ようこそ
付録自分で書いたデモ
const Canvas = document.getElementById('canvas') class Rectangular { コンストラクタ ( ctx, { 上 = 0, 左 = 0, 幅 = 30, 高さ = 50, 背景 = '赤' } ) { this.ctx = ctx これ。 top = 上 this.left = 左 this.width = 幅 this.height = 高さ this.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() } 調整 (左、上) { this.left += left this.top += top } } class Circle { コンストラクター ( ctx, { center = [], radius = 10, background = 'blue' } ) { this.ctx = ctx this.center = [center[0] === 未定義の半径 : center[0]、center[1] === 未定義の半径 : center[1]] this.radius = 半径 this.background = 背景 }絵画() { 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() } 調整 (左、上) { this.center[0] += 左 this.center[1] += トップ } } クラスデモ {コンストラクター (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) } ペイント () { 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 selectedIndex = null this.renderList.forEach((it, Index) => { it.painting() if (this.ctx.isPointInPath(startX, startY)) { selectedIndex =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 ('マウスアップ', マウスアップイベント) } } } const yes = 新しいデモ(キャンバス) .rectangular({}) .rectangular({上: 60, 左: 60, 背景: '青'}) .rectangular({上: 30, 左: 20, 背景: '緑'}) .circle() .circle ({中心: [100, 30]、背景: '赤'、半径: 5}) .painting()
以上がこの記事の全内容です。皆様の学習のお役に立てれば幸いです。また、VeVb Wulin Network をご支援いただければ幸いです。