As a front-end, adding events to elements is a common thing. But in Canvas, anything drawn on it is unobtainable, let alone adding events, so are we helpless about it? Of course not! We must have used many Canvas frameworks in our daily projects. We found that events have been used very maturely in these frameworks, and there have been no particularly serious problems. So what we can be sure of is that events are not an untouchable thing in Canvas.
a fool's approachWe all know that when an element triggers an event, the position of the mouse is basically above the element, so we naturally think of comparing the current mouse position with the position occupied by the object, so that we can get the Whether the object should trigger the event. This method is relatively simple, so I won't use code to demonstrate it, but since I call it a fool's method, it is obvious that it is not an effective solution. Because the position occupied by an object is not necessarily very easy to obtain. If it is a rectangle, circle, etc., we can also obtain its position through some simple formulas. However, in polygons with complex points, or even some sides of the polygon are Curved, obviously, it is extremely complicated and difficult for us to obtain the position it occupies at this time, so this method is only suitable for use in some demos, and is not suitable for most Condition.
a smarter waySince the above method has hit a wall, we can only find another way. When browsing the CanvasAPI, I found a method isPointInPath, which seems to be the medicine we are looking for.
Introducing isPointInPathThe role of isPointInPath: As the name suggests, we can intuitively know that this method is used to determine whether a point is in a path.
The input and output parameters of isPointInPath are: ctx.isPointInPath([path, ]x, y [, fillRule]). This method has 4 parameters, of which path and fillRule are optional, and x and y are required. We introduce the 4 parameters in turn.
path: When I saw this parameter, I initially thought it was the return value of beginPath or closePath. Unfortunately, these two methods did not return a value. After checking the information, I found that it was the object of the new Path2D constructor. Specific usage of Path2D constructor. However, it is a pity that this method may be due to compatibility issues. Currently, some open source frameworks have not been used.
x, y: These two parameters are easy to understand, they are the distance between the x-axis and the y-axis. It should be noted that their relative position is the upper left corner of the Canvas.
fillRule: nonzero (default), evenodd. The non-zero wrapping rule and the odd-even rule are rules in graphics for determining whether a point is within a polygon. The non-zero wrapping rule is the default rule of Canvas. If you want to know more about these two rules, you can check the information yourself, so I won’t introduce them here.
After introducing the input parameters above, everyone can probably guess the output parameters of the isPointInPath method, which are true and false.
Using isPointInPathAfter introducing the isPointInPath method in the previous section, let's use it now.
Let’s start with a simple 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)) })
As shown in the figure, the gray part is the area occupied by the Canvas, and the black part is the area where we actually added the event. After we click on the black area, it actually does what we want, and the printed value is true. It seems that Canvas event monitoring can be solved simply, but is it really that simple? Obviously impossible! Let's take another example. There are two areas at this time, and we need to bind different events to them:
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)) })
At this time, the result is no longer what we expected. When the black area is clicked, the printed value is false, and when the red area is clicked, the printed value is true.
In fact, the reason is very simple. Because of the above code, we actually created two Paths, and the isPointInPath method actually only detects whether the current point is in the last Path. In the example, the red area is the last Path, so only when the red area is clicked, isPointInPath method can be judged as true. Now let's transform the code:
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)) }) })
We have made a big modification to the above code. We put each Path into a separate function and push them into an array. When the click event is triggered, we clear the Canvas, traverse the array and redraw, and make a judgment every time a Path is drawn. Therefore, when calling the isPointInPath method, we can obtain the last current Path in real time, and then judge the current point. Among the Path.
Now we have indirectly implemented separate event monitoring for each Path, but the way it is implemented requires redrawing again and again. So is there a way to monitor events without redrawing?
First of all, we need to know that the reason for redrawing again and again is because the isPointInPath method is the last Path to be monitored. However, when we introduced this method, we said that its first parameter is a Path object. When we pass this parameter , Path will no longer take the last Path but use the Path we passed in. Now let's do a demo to verify its feasibility:
const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const path1 = new Path2D(); path1.rect(10, 10, 100,100); ctx.fill(path1) const path2 = new Path2D(); path2.moveTo(220, 60); path2.arc(170, 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)) })
As shown in the picture above, we clicked on the left graphic and printed true and false; clicked on the right graphic and printed false and true. The printed results show that there is no problem, but since its compatibility needs to be improved, it is currently recommended to use the redraw method to listen for events.
ConclusionThe event monitoring of Canvas is basically covered here. The principle is very simple and everyone should be able to master it.
github address, welcome to start
appendixA demo written by myself
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 chosenIndex = null this.renderList.forEach((it, index) => { it.painting() if (this.ctx.isPointInPath(startX, startY)) { chosenIndex = 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()
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope everyone will support VeVb Wulin Network.