프런트엔드에서는 요소에 이벤트를 추가하는 것이 일반적입니다. 하지만 Canvas에서는 이벤트 추가는커녕 거기에 그려진 어떤 것도 얻을 수 없으므로 우리는 그것에 대해 속수무책입니까? 물론 그렇지 않습니다! 우리는 일상적인 프로젝트에서 많은 Canvas 프레임워크를 사용했을 것입니다. 우리는 이러한 프레임워크에서 이벤트가 매우 성숙하게 사용되었으며 특별히 심각한 문제가 없다는 것을 발견했습니다. 따라서 우리가 확신할 수 있는 것은 이벤트가 캔버스에서 건드릴 수 없는 것이 아니라는 것입니다.
바보 같은 접근우리는 요소가 이벤트를 트리거할 때 기본적으로 마우스 위치가 요소 위에 있다는 것을 알고 있으므로 자연스럽게 현재 마우스 위치와 개체가 차지하는 위치를 비교하여 개체가 있어야 하는지 여부를 얻을 수 있다고 생각합니다. 이벤트를 발동시킵니다. 이 방법은 상대적으로 간단하므로 코드를 사용하여 설명하지는 않겠지만, 바보의 방법이라고 부르기 때문에 효과적인 해결책이 아니라는 것은 분명합니다. 물체가 차지하는 위치를 얻는 것이 반드시 쉽지는 않기 때문에 직사각형, 원 등의 경우 몇 가지 간단한 공식을 통해 위치를 얻을 수도 있습니다. 다각형은 곡선입니다. 현재로서는 다각형이 차지하는 위치를 얻는 것이 매우 복잡하고 어렵습니다. 따라서 이 방법은 일부 데모에서만 사용하기에 적합하며 대부분의 조건에는 적합하지 않습니다.
더 똑똑한 방법위의 방법은 벽에 부딪혔기 때문에 다른 방법을 찾을 수밖에 없습니다. CanvasAPI를 탐색하던 중 isPointInPath 메소드를 발견했는데, 이것이 우리가 찾고 있는 약인 것 같습니다.
isPointInPath 소개isPointInPath의 역할: 이름에서 알 수 있듯이 이 메서드가 경로에 점이 있는지 여부를 확인하는 데 사용된다는 것을 직관적으로 알 수 있습니다.
isPointInPath의 입력 및 출력 매개변수는 다음과 같습니다: ctx.isPointInPath([path, ]x, y [, fillRule]) 이 메소드에는 4개의 매개변수가 있으며, 그 중 path 및 fillRule은 선택사항이고 x 및 y는 필수입니다. 4가지 매개변수를 차례로 소개합니다.
path: 처음에 이 매개변수를 봤을 때 BeginPath 또는 closePath의 반환 값인 줄 알았는데, 아쉽게도 이 두 메서드는 값을 반환하지 않았는데, 정보를 확인한 결과 새 Path2D 생성자의 개체라는 것을 알았습니다. Path2D 생성자의 특정 사용법. 하지만 이 방법은 호환성 문제로 인해 아쉽게도 현재 일부 오픈소스 프레임워크가 사용되지 않고 있습니다.
x, y: 이 두 매개변수는 이해하기 쉽습니다. x축과 y축 사이의 거리입니다. 상대 위치는 캔버스의 왼쪽 상단입니다.
fillRule: 0이 아님(기본값), evenodd. 0이 아닌 래핑 규칙과 홀수-짝수 규칙은 점이 다각형 내에 있는지 여부를 결정하기 위한 그래픽의 규칙입니다. 0이 아닌 래핑 규칙은 Canvas의 기본 규칙입니다. 이 두 가지 규칙에 대해 더 알고 싶으시면 직접 정보를 확인하시면 되므로 여기서는 소개하지 않겠습니다.
위의 입력 매개변수를 소개한 후에는 누구나 isPointInPath 메소드의 출력 매개변수(true 및 false)를 추측할 수 있습니다.
isPointInPath 사용이전 섹션에서 isPointInPath 메서드를 소개한 후 이제 이를 사용해 보겠습니다.
간단한 데모부터 시작해 보겠습니다.
const 캔버스 = 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가 차지하는 영역이고, 검은색 부분은 실제로 이벤트를 추가한 영역입니다. 검은색 영역을 클릭하면 실제로 원하는 대로 동작하고 출력되는 값이 나옵니다. 사실이다. 캔버스 이벤트 모니터링은 간단하게 해결될 것 같지만, 정말 그렇게 간단할까요? 분명히 불가능합니다! 또 다른 예를 들어보겠습니다. 현재 두 가지 영역이 있으며 여기에 서로 다른 이벤트를 바인딩해야 합니다.
const 캔버스 = 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입니다.
사실 이유는 매우 간단합니다. 위의 코드로 인해 실제로 두 개의 Path가 생성되었으며, isPointInPath 메서드는 실제로 현재 지점이 마지막 Path에 있는지 여부만 감지합니다. 따라서 빨간색 영역을 클릭해야만 isPointInPath 메소드가 true로 판단될 수 있습니다. 이제 코드를 변환해 보겠습니다.
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= '빨간색' 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를 실시간으로 얻을 수 있습니다. 현재 지점을 판단합니다.
이제 각 Path에 대해 별도의 이벤트 모니터링을 간접적으로 구현했지만 구현 방식상 다시 그려야 하는 경우가 많습니다. 그렇다면 다시 그리지 않고도 이벤트를 모니터링할 수 있는 방법이 있을까요?
먼저, 계속해서 다시 그리는 이유는 isPointInPath 메서드가 모니터링할 마지막 Path이기 때문이라는 점을 알아야 합니다. 그러나 이 메서드를 도입할 때 첫 번째 매개 변수가 Path 개체라고 말했습니다. 이 매개변수를 전달하면 Path는 더 이상 마지막 Path를 사용하지 않고 우리가 전달한 Path를 사용합니다. 이제 실행 가능성을 확인하기 위해 데모를 실행해 보겠습니다.
const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const path1 = new Path2D()(10, 10, 100,100) const path2 = 새로운 Path2D(); path2.moveTo(220, 60); 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)) })
위 그림에서 볼 수 있듯이 왼쪽 그래픽을 클릭하고 true와 false를 인쇄했습니다. 오른쪽 그래픽을 클릭하고 false와 true를 인쇄했습니다. 인쇄된 결과에는 문제가 없는 것으로 나오나, 호환성 개선이 필요하므로 현재는 redraw 방식을 사용하여 이벤트를 수신하는 것을 권장합니다.
결론Canvas의 이벤트 모니터링은 기본적으로 여기에서 다룹니다. 원리는 매우 간단하며 누구나 마스터할 수 있어야 합니다.
github 주소, 시작하신 것을 환영합니다
충수직접 작성한 데모
const 캔버스 = document.getElementById('canvas') 클래스 직사각형 { 생성자 ( ctx, { top = 0, left = 0, width = 30, height = 50, background = 'red' } ) { this.ctx = ctx this. top = 상단 this.left = 왼쪽 this.width = 너비 this.height = 높이 this.ground = 배경 } 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 += 왼쪽 this.top += 위쪽 } } class Circle { 생성자 ( ctx, { center = [], radius = 10, background = 'blue' } ) { this.ctx = ctx this.center = [center[0] === 정의되지 않음 ? 반경 : 중심[0], 중심[1] === 정의되지 않음 ? 반경 : 중심[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() } adjust (왼쪽, 위쪽) { this.center[0] += 왼쪽 this.center[1] += 위쪽 } } 클래스 데모 { 생성자 (캔버스) { this.canvasInfo = canvas.getBoundingClientRect() this.renderList = [] this.ctx = canvas.getContext('2d') this.canvas = 캔버스 this.Rectangular = (config) => { let target = new 직사각형(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 ('mouseup', mouseupEvent) } } } const yes = 새 데모(캔버스) .직사각형({}) .직사각형({상단: 60, 왼쪽: 60, 배경: '파란색'}) .직사각형({상단: 30, 왼쪽: 20, 배경: '녹색'}) .circle() .circle ({중심: [100, 30], 배경: '빨간색', 반경: 5}) .painting()
위의 내용은 이 기사의 전체 내용입니다. 모든 분들의 학습에 도움이 되기를 바랍니다. 또한 모든 분들이 VeVb Wulin Network를 지지해 주시길 바랍니다.