In the application of Canvas implementation, there will be such a kind of scene, when clicking a geometric figure in the Canvas, some interactive operations will be triggered. Canvas does not support adding events to graphical elements within the Canvas, which requires some math to solve. First, I will show you how to obtain the coordinates of the click position on the canvas, which will be used in the decision scheme later.
To obtain the coordinate
Although Canvas does not support adding events to graphic elements inside the Canvas, we can listen to click events of Canvas elements themselves to calculate the coordinates of click positions on the Canvas.
canvas.addEventListener('click'.(event) = > {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const point = {
x,
y,
}
console.log(point); // Print the click position coordinates
})
Copy the code
If the user wants to click on an image, the position of the click must be inside the image, so the problem becomes how to determine that a point is inside the image. This paper mainly introduces two kinds of judgment methods with different ideas, one is the mathematical method based on computational geometry, the other is the detection method based on pixel color value.
geometry
To simplify matters, we will only discuss polygon scenarios. There are two ways to judge whether a point is in a polygon: Crossing Number and Winding Number.
Cross method
Cross number method: take rays at a point that is inside the polygon if the rays intersect the sides of the polygon an odd number of times, otherwise outside the polygon. As shown below:
The code implementation is as follows:
function getCrossingNumber(point, lines) {
let count = 0;
for (let i = 0; i < lines.length; i += 1) {
// o, d are the starting and ending points of a polygon
const { o, d } = lines[i];
// There is an intersection between the beginning and the end of the horizontal ray
if ((o.y > point.y) ^ (d.y > point.y)) {
// x = (y - y0) / k + x0
const x = (point.y - o.y) * (d.x - o.x) / (d.y - o.y) + o.x;
if (x > point.x) {
count += 1; }}}return count;
}
Copy the code
The fabric.js library uses the cross number method to determine whether a point is inside a polygon. For details, see github.com/fabricjs/fa…
The crossover method can produce errors in certain scenarios, such as when the geometry overlaps internally. Comparatively speaking, the loop method is more accurate.
Surrounding the method
Surround number method: make horizontal right ray at a point, if the edge of a polygon passes through the ray from bottom up, then the surround number is added by one; If one of the sides of a polygon passes through the ray from the top down, the number of circles is reduced by one; The final circle number is inside the polygon if it is not zero, otherwise it is outside the polygon. As shown below:
It’s relatively easy for a human to tell if it’s going up (or down) through a ray, but the code implementation can be a little complicated, so you need to convert this up and down relationship to left and right. As shown in the figure below, if an edge passes up through the ray, then P is to the left of the edge AB; For a downward edge, P is to the right of AB.
Using the right hand rule, when P is to the left of AB, the normal vectors of AB and AP are outward; When P is to the right of AB, the normal vectors of AB and AP are in. The normal vectors of AB and AP can be computed by the cross product of the vectors:
AB = (x1, y1, 0) AB × AP = (x2, y2, 0) AB × AP = (x1y2-x2y1)k where, k is the z-axis unit vector, x1y2-x2y1 plus or minus represents the directionCopy the code
Then the following results are obtained:
- When x1y2-x2y1 is greater than 0, P is to the left of AB and the number of circles is +1
- When x1y2-x2y1 = 0, P is on AB and the number of circles is constant
- When x1y2-x2y1 is less than 0, P is to the right of AB, and the number of circles is -1
Through the above two algorithms, it can determine whether the clicking position on Canvas is on a polygon, and then trigger the relevant interaction. The loop method requires some math to understand, but the code is not complicated. The cross number method and the circle number are mentioned above to determine whether a point is in the geometry. In addition to these two methods, another detection scheme based on the color value of pixels will be introduced below.
Pixel detection
The basic principle of
When users click on Canvas Canvas, they can determine whether to click on a graph by the pixel of the click position. For example, in the image below, the color of the rectangle is orange, while the color of the outside of the rectangle is transparent. If we figure out that the color of P is orange, we can infer that P is sitting on a rectangle.
This is the ideal scenario, but it’s a lot more complicated than that. For example, in the picture below, there is no way to make a judgment when the Canvas renders the graphics with the same color.
In most Canvas libraries (such as fabric.js and create.js), data such as graph attributes and state are usually stored in memory, which can be directly retrieved and used in the next rendering. Similarly, with this data, we can also draw a graph in a new Canvas, which simplifies the problem to the ideal situation mentioned above, where we can determine the point by color. Since the background of the new Canvas is completely transparent, it only needs to test the transparency of the color. If the transparency of the detected point is 0, the point does not hit the graph. In the following figure, the Canvas on the left is used for normal display, while the Canvas on the right is used for detection and is not displayed to the user.
The specific implementation
Create.js uses this transparency contrast method to determine whether a point is on a graph. Here’s some code for create.js to help you understand it.
/** * Tests whether the display object intersects the specified point in local coordinates (ie. draws a pixel with alpha > 0 at * the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation of the display object. * * Please note that shape-to-shape collision is not currently supported by EaselJS. *@method hitTest
* @param {Number} x The x position to check in the display object's local coordinates.
* @param {Number} y The y position to check in the display object's local coordinates.
* @return {Boolean} A Boolean indicating whether a visible portion of the DisplayObject intersect the specified
* local Point.
*/
p.hitTest = function(x, y) {
// DisplayObject._hitTestContext is a new Context
var ctx = DisplayObject._hitTestContext;
// Perform the matrix transformation to move the detected point to the position (0, 0)
ctx.setTransform(1.0.0.1, -x, -y);
// Draw the graph on the new CTX
this.draw(ctx, ! (this.bitmapCache && ! (this.bitmapCache._cacheCanvas instanceof WebGLTexture) ));
// Get the transparency of position (0, 0). If the transparency is greater than 1, the detected point (x, y) is on the graph
var hit = ctx.getImageData(0.0.1.1).data[3] > 1;
ctx.setTransform(1.0.0.1.0.0);
ctx.clearRect(0.0.2.2);
return hit;
};
Copy the code
However, it is worth noting that this detection method fails when the filling color of the graph is completely transparent, and some special treatment is required. The solution to create.js is to bind the transparent figure to an opaque figure of the same size and use this figure for detection. The above code is available at github.com/CreateJS/Ea… , you can check it yourself.
The resources
- Algorithm for identifying points in polygons (Winding Number Elaboration)
- Fabric.js detects whether the graph is clicked source
- Create.js detects the source of whether the graph is clicked
Search ikoofe on wechat, and the public account “KooFE Front-end team” releases front-end technology articles from time to time.