Como interfaz, agregar eventos a elementos es algo común. Pero en Canvas, cualquier cosa dibujada en él es inalcanzable, y mucho menos agregar eventos, entonces, ¿estamos indefensos al respecto? ¡Por supuesto que no! Debemos haber utilizado muchos marcos de Canvas en nuestros proyectos diarios. Descubrimos que los eventos se han utilizado con mucha madurez en estos marcos y no ha habido problemas particularmente graves. Entonces, de lo que podemos estar seguros es que los eventos no son algo intocable en Canvas.
un enfoque tontoTodos sabemos que cuando un elemento desencadena un evento, la posición del mouse es básicamente encima del elemento, por lo que naturalmente pensamos en comparar la posición actual del mouse con la posición ocupada por el objeto, para que podamos obtener si el objeto debe desencadenar el evento. Este método es relativamente simple, por lo que no usaré código para demostrarlo, pero como lo llamo método tonto, es obvio que no es una solución efectiva. Debido a que la posición que ocupa un objeto no necesariamente es muy fácil de obtener si es un rectángulo, círculo, etc., también podemos obtener su posición mediante algunas fórmulas simples. Sin embargo, en polígonos con puntos complejos, o incluso algunos lados. el polígono es curvo, obviamente, es extremadamente complicado y difícil para nosotros obtener la posición que ocupa en este momento, por lo que este método solo es adecuado para usar en algunas demostraciones y no es adecuado para la mayoría de las condiciones.
una manera más inteligenteDado que el método anterior se ha topado con un muro, solo podemos encontrar otra manera. Al navegar por CanvasAPI, encontré un método isPointInPath, que parece ser la medicina que estamos buscando.
Presentamos isPointInPathLa función de isPointInPath: como sugiere el nombre, podemos saber intuitivamente que este método se utiliza para determinar si un punto está en una ruta.
Los parámetros de entrada y salida de isPointInPath son: ctx.isPointInPath([path, ]x, y [, fillRule]). Este método tiene 4 parámetros, de los cuales path y fillRule son opcionales, y xey son obligatorios. Introducimos los 4 parámetros uno por uno.
ruta: cuando vi este parámetro, inicialmente pensé que era el valor de retorno de beginPath o closePath. Desafortunadamente, estos dos métodos no devolvieron un valor. Después de verificar la información, descubrí que era el objeto del nuevo constructor Path2D. Uso específico del constructor Path2D. Sin embargo, es una lástima que este método pueda deberse a problemas de compatibilidad. Actualmente, algunos marcos de código abierto no se han utilizado.
x, y: estos dos parámetros son fáciles de entender, son la distancia entre el eje x y el eje y. Cabe señalar que su posición relativa es la esquina superior izquierda del lienzo.
fillRule: distinto de cero (predeterminado), par impar. La regla de ajuste distinto de cero y la regla par-impar son reglas en los gráficos para determinar si un punto está dentro de un polígono. La regla de ajuste distinto de cero es la regla predeterminada de Canvas. Si desea saber más sobre estas dos reglas, puede consultar la información usted mismo, por lo que no las presentaré aquí.
Después de introducir los parámetros de entrada anteriores, probablemente todos puedan adivinar los parámetros de salida del método isPointInPath, que son verdadero y falso.
Usando isPointInPathDespués de presentar el método isPointInPath en la sección anterior, usémoslo ahora.
Comencemos con una demostración simple:
const lienzo = document.getElementById('canvas') const ctx = lienzo.getContext('2d') ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10, 50) ctx.lineTo(50, 50 ) ctx.lineTo(50, 10) ctx.fillStyle= 'negro' ctx.fill() ctx.closePath() lienzo.addEventListener('clic', función (e) { const canvasInfo = canvas.getBoundingClientRect() console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top)) })
Como se muestra en la figura, la parte gris es el área ocupada por el lienzo y la parte negra es el área donde realmente agregamos el evento. Después de hacer clic en el área negra, realmente hace lo que queremos y el valor impreso. es verdad. Parece que el monitoreo de eventos de Canvas se puede resolver de manera simple, pero ¿es realmente así de simple? ¡Obviamente imposible! Tomemos otro ejemplo. Hay dos áreas en este momento y debemos vincularles diferentes eventos:
const lienzo = document.getElementById('canvas') const ctx = lienzo.getContext('2d') ctx.beginPath() ctx.moveTo(10, 10) ctx.lineTo(10, 50) ctx.lineTo(50, 50 ) ctx.lineTo(50, 10) ctx.fillStyle= 'negro' ctx.fill() ctx.closePath() ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo(150, 100) ctx.fillStyle = 'rojo' ctx.fill() ctx.closePath() canvas.addEventListener('clic', función (e) { const canvasInfo = canvas.getBoundingClientRect() console.log(ctx.isPointInPath(e.clientX - canvasInfo.left, e.clientY - canvasInfo.top)) })
En este momento, el resultado ya no es el que esperábamos. Cuando se hace clic en el área negra, el valor impreso es falso y cuando se hace clic en el área roja, el valor impreso es verdadero.
De hecho, la razón es muy simple. Debido al código anterior, en realidad creamos dos Rutas, y el método isPointInPath en realidad solo detecta si el punto actual está en la última Ruta. En el ejemplo, el área roja es la última Ruta. por lo tanto, solo cuando se hace clic en el área roja, el método isPointInPath puede considerarse verdadero. Ahora transformemos el código:
const lienzo = document.getElementById('canvas') const ctx = lienzo.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= 'negro' ctx.fill() } función draw2 () { ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(100, 150) ctx.lineTo(150, 150) ctx.lineTo (150, 100) ctx.fillStyle= 'rojo' 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)) }) })
Hemos realizado una gran modificación al código anterior. Colocamos cada ruta en una función separada y las insertamos en una matriz. Cuando se activa el evento de clic, limpiamos el lienzo, recorremos la matriz y lo volvemos a dibujar, y juzgamos cada vez que se dibuja una ruta. Por lo tanto, al llamar al método isPointInPath, podemos obtener la última ruta actual en tiempo real y luego. juzgar el punto actual.
Ahora hemos implementado indirectamente un monitoreo de eventos separado para cada Ruta, pero la forma en que se implementa requiere volver a dibujarlo una y otra vez. Entonces, ¿hay alguna manera de monitorear eventos sin volver a dibujarlo?
En primer lugar, debemos saber que el motivo para volver a dibujar una y otra vez es porque el método isPointInPath es la última Ruta que se monitoreará. Sin embargo, cuando presentamos este método, dijimos que su primer parámetro es un objeto Ruta. Si pasa este parámetro, Path ya no tomará el último Path, sino que usará el Path que le pasamos. Ahora hagamos una demostración para verificar su viabilidad:
const lienzo = document.getElementById('canvas') const ctx = canvas.getContext('2d') const ruta1 = new Path2D(); const ruta2 =; nueva Ruta2D(); ruta2.moveTo(220, 60); ruta2.arc(170, 60, 50, 0, 2 * Math.PI); ctx.stroke(ruta2) lienzo.addEventListener('hacer clic', función (e) { console.log(ctx.isPointInPath(ruta1, e.clientX, e.clientY) ) console.log(ctx.isPointInPath(ruta2, e.clientX, e.clientY)) })
Como se muestra en la imagen de arriba, hicimos clic en el gráfico de la izquierda e imprimimos verdadero y falso; hicimos clic en el gráfico de la derecha e imprimimos falso y verdadero; Los resultados impresos muestran que no hay ningún problema, pero dado que es necesario mejorar su compatibilidad, actualmente se recomienda utilizar el método de redibujado para escuchar eventos.
ConclusiónAquí se trata básicamente el monitoreo de eventos de Canvas. El principio es muy simple y todos deberían poder dominarlo.
dirección de github, bienvenido a comenzar
apéndiceUna demostración escrita por mí.
const lienzo = document.getElementById('canvas') clase rectangular { constructor ( ctx, { arriba = 0, izquierda = 0, ancho = 30, alto = 50, fondo = 'rojo' } ) { this.ctx = ctx this. arriba = arriba this.left = izquierda this.width = ancho this.height = alto this.background = fondo } pintura () { 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() } ajustar (izquierda, arriba) { this.left += izquierda this.top += top } } círculo de clase { constructor ( ctx, { centro = [], radio = 10, fondo = 'azul' } ) { this.ctx = ctx this.center = [centro[0] === radio indefinido: centro[0], centro[1] === radio indefinido: centro[1]] this.radius = radio this.fondo = fondo } cuadro () { 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() } ajustar (izquierda, arriba) { this.center[0] += izquierda this.center[1] += top } } demostración de clase { constructor (lienzo) { this.canvasInfo = canvas.getBoundingClientRect() this.renderList = [] this.ctx = canvas.getContext('2d') this.canvas = lienzo this.rectangular = (config) => { let target = new rectangular(this.ctx, {...config}) this.addRenderList(target) devuelve esto } this.circle = (config) => { let target = new círculo(this.ctx, {...config}) this.addRenderList(destino) devuelve esto } this.addEvent() } addRenderList (destino) { this.renderList.push(destino) } itemToLast (índice) { const lastItem = this.renderList.splice(index, 1)[0] this.renderList.push(lastItem) } pintura () { 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, índice) => { it.painting() if (this.ctx.isPointInPath(startX, startY)) { índice elegido = índice } }) if (indice elegido! == nulo) { this.itemToLast(índice elegido) } 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 sí = nueva demostración (lienzo) .rectangular({}) .rectangular({arriba: 60, izquierda: 60, fondo: 'azul'}) .rectangular({arriba: 30, izquierda: 20, fondo: 'verde'}) .circle() .circle ({centro: [100, 30], fondo: 'rojo', radio: 5}) .painting()
Lo anterior es el contenido completo de este artículo. Espero que sea útil para el estudio de todos. También espero que todos apoyen VeVb Wulin Network.