DOM is a very important part of the Web front-end field. DOM is used not only when processing HTML elements, but also in graphic programming. For example, in SVG drawing, various graphics are inserted into the page in the form of DOM nodes, which means that graphics can be manipulated using DOM methods. For example, if there is a element, you can directly use jquery to add a click event $('#p1').click(function(){…})". However, this DOM processing method is not suitable for HTML5 It is no longer applicable in Canvas. Canvas uses another set of mechanisms. No matter how many graphics are drawn on Canvas, Canvas is a whole. The graphics themselves are actually part of Canvas and cannot be obtained separately, so it cannot be directly given to someone. Added JavaScript events to graphics
.
Limitations of Canvas
In Canvas, all graphics are drawn on the frame. The drawing method will not output the drawn graphic elements as a return value, and js cannot obtain the drawn graphic elements. For example:
cvs = document.getElementById('mycanvas' );
ctx = canvas.getContext('2d');
theRect = ctx.rect(10, 10, 100, 100);
ctx.stroke();
console.log( theRect); //undefined
This code draws a rectangle in the canvas tag. First of all, you can see that the rect method for drawing graphics has no return value. If you open the browser's developer tools, you can also see that no content has been added inside the canvas tag, and the canvas element and the current context obtained in js do not have any content indicating the new graphics.
So, the DOM methods commonly used on the front end are not applicable in canvas. For example, if you click on the rectangle in the Canvas above, you will actually click on the entire Canvas element.
Bind events to the Canvas element
Since the event can only reach the Canvas element level, if you want to go further and identify which graphic inside the Canvas the click occurred on, you need to add code for processing. The basic idea is: bind an event to the Canvas element. When the event occurs, check the position of the event object, and then check which graphics cover that position. For example, in the above example, a rectangle was drawn, which covers the range of 10-110 on the x-axis and 10-110 on the y-axis. As long as the mouse is clicked within this range, it can be regarded as clicking on the rectangle, and the click event that needs to be processed by the rectangle can be manually triggered. The idea is actually relatively simple, but the implementation is still a bit complicated. Not only must the efficiency of this judgment process be considered, but event types also need to be re-judged in some places, and the capture and bubbling mechanism inside Canvas must be redefined.
The first thing to do is to bind an event to the Canvas element. For example, if you want to bind a click event to a graphic inside the Canvas, you need to proxy the event through the Canvas element:
cvs = document.getElementById('mycanvas' );
cvs.addEventListener('click', function(e){
//...
}, false);
Next, you need to determine the location where the event object occurs. The layerX and layerY attributes of the event object e represent the coordinates in the Canvas internal coordinate system. However, Opera does not support this attribute, and Safari plans to remove it, so we need to make some compatible writing methods:
function getEventPosition(ev){
var x, y;
if (ev.layerX || ev.layerX == 0) {
x = ev.layerX;
y = ev.layerY;
} else if (ev. offsetX || ev.offsetX == 0) { // Opera
x = ev.offsetX;
y = ev.offsetY;
}
return {x: x, y: y};
}
//Note: To use the above function, you need to set the position of the Canvas element to absolute.
Now that we have the coordinate position of the event object, we need to determine which graphics in the Canvas cover this coordinate.
isPointInPath method
The isPointInPath method of Canvas can determine whether the current context graphic covers a certain coordinate, such as:
cvs = document.getElementById('mycanvas' );
ctx = canvas.getContext('2d');
ctx.rect(10, 10, 100, 100);
ctx.stroke();
ctx.isPointInPath(50, 50); //true
ctx.isPointInPath(5, 5); //false
Next, add an event judgment to determine whether a click event occurs on the rectangle:
cvs.addEventListener('click', function (e){
p = getEventPosition(e);
if(ctx.isPointInPath(p.x, p.y)){
//Clicked the rectangle
}
}, false);
The above is the basic method of handling Canvas events, but the above code has limitations. Since the isPointInPath method only determines the path in the current context, when multiple graphics have been drawn in the Canvas, only The event can be judged based on the context of the last graphic, for example:
cvs = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.rect(10, 10, 100, 100);
ctx.stroke();
ctx.isPointInPath(20, 20); //true
ctx.beginPath();
ctx.rect(110, 110, 100 , 100);
ctx.stroke();
ctx.isPointInPath(150, 150); //true
ctx.isPointInPath(20, 20); //false
As you can see from the above code, the isPointInPath method can only identify the graphics path in the current context, and the previously drawn path cannot be judged retrospectively. The solution to this problem is: when a click event occurs, redraw all graphics, and use the isPointInPath method for each one drawn to determine whether the event coordinates are within the coverage of the graphics.
Loop repaint and event bubbling
In order to achieve cyclic redrawing, the basic parameters of the graphics must be saved in advance:
arr = [
{x:10, y:10, width:100, height:100},
{x:110, y:110, width: 100, height:100}
];
cvs = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
draw( );
function draw(){
ctx.clearRech(0, 0, cvs.width, cvs.height);
arr.forEach(function(v){
ctx. beginPath();
ctx.rect(v.x, v.y, v.width, v.height);
ctx.stroke();
});
}
The above code saves the basic parameters of the two rectangles in advance. Each time the draw method is called, these basic parameters will be called in a loop to draw the two rectangles. The clearRect method is also used here to clear the canvas when redrawing. The next thing to do is to add event delegates and use the isPointInPath method on each context when redrawing:
cvs.addEventListener('click', function (e){
p = getEventPosition(e);
draw(p);
}, false);
When the event occurs, pass the coordinates of the event object to draw method processing. Some small changes need to be made to the draw method here:
function draw(p){
var who = [];
ctx.clearRech(0, 0, cvs.width, cvs.height);
arr.forEach(function(v, i){
ctx.beginPath();
ctx.rect(v.x, v.y, v.width, v.height);
ctx.stroke();
if(p && ctx.isPointInPath(p.x, p.y)){
// If event coordinates are passed in, use isPointInPath to determine
//If the current environment covers the coordinates, put the index value of the current environment into the array
who.push(i);
}
});
//According to the index value in the array, the corresponding element can be found in the arr array.
return who;
}
In the above code, when the click event occurs, the draw method will perform a redraw, and during the redraw process, check whether each graphic covers the event coordinates. If the judgment is true, the graphic is deemed to be clicked, and Put the index value of the graphic into the array, and finally use the array as the return value of the draw method. Under this processing mechanism, if there are N graphics in the Canvas, part of them overlaps, and the click event happens to occur in this overlapping area, then there will be N members in the return array of the draw method. At this time, it is a bit similar to the situation of event bubbling. The last member of the array is at the top of the Canvas, and the first member is at the bottom. We can think that the top member is e.target, and the other members are e.target. The node passed to during the bubble process. Of course, this is just the simplest processing method. If you really want to simulate DOM processing, you need to set a parent-child relationship for the graphics.
The above is the basic method of Canvas event handling. In actual application, how to cache graphics parameters, how to perform loop redrawing, and how to handle event bubbling all need to be dealt with according to the actual situation. In addition, click is an event that is relatively easy to handle. The relatively troublesome events are mouseover, mouseout and mousemove. Since once the mouse enters the Canvas element, the mousemove event always occurs, so if you want to set mouseover or mouseout separately for a certain graphic , you also need to record the route of the mouse movement and set the entry and exit status for the graphics. As the processing steps become more complex, increased attention must be paid to performance issues.