Problem
Those who are engaged in the development of graphics using JavaScript + Canvas have long noticed the problem of handling mouse events on any elements of the graphics.
There are several solutions to the problem:
- Do not process them at all, that is, your graphics are non-interactive and you need nothing
- Calculate a rectangle for each shape, store it in memory, and trigger events when the cursor hits these rectangles
- Approach each element of the graphics individually, applying different mathematical formulas for rectangles, circles, lines, etc.
All these methods have the right to life in certain circumstances, but when events need to be discovered (we dismiss option 1), when the figures are often not rectangular, they have turns, and other transformations (option 2 also does not fit), when the figures are not geometrically correct, as for example, lines smoothed by splines, polygons with concave edges (option 3 was also forgotten), and most importantly, when these figures become innumerable, and to store the coordinates of each, sorting them out for each MouseMove becomes overhead, another method comes to the rescue.
Solution approach
We need to know exactly up to the pixel whether the cursor hit our shape or not. To do this, take the following:
When creating a shape, assign it a unique numeric identifier, for example, 1. Now we convert this identifier to the color in RGB HEX notation -
#000001
.
')
Let's create a new canvas element for the layer on which the shape is located, but we will not add it to the DOM, this is our background canvas.
When drawing a shape on the main canvas, we will draw it on the background, but with the color resulting from its identifier (including the border lines).
Now it becomes clear that if the order of drawing is observed, the figures on the background canvas will overlap each other in the same order as on the real one, and when you move the mouse cursor over the main canvas, you can find out the coordinates of the pixel color of the background canvas, which is the shape identifier, which allows us to unequivocally say that the cursor entered a figure or left it.
Underwater rocks
When the mouse moves, we determine the color of the pixel below it, and if it is opaque
(Alpha = 255)
, we define the identifier of the shape and work with events.
But not everything is so rosy. There are 3 main problems:
1. Smoothing
The fact is that all browsers by default use anti-aliasing when rendering, which means that not all pixels of the shape will be completely opaque and of the color that we need.
Decision:It is necessary to draw the borders of the shape 2 pixels thicker (or just 2 pixels in the absence of this border). This will create additional noise in the surrounding pixels, but since for us they are not significant, and also not visible to the user, this can be neglected.
2. Merge colors
When intersecting 2 shapes, the same smoothing can create completely opaque pixels, the color of which will be a transition from the color of one shape to the color of another. In this case, there is a small probability that this color will indicate a completely different shape, which is located in a completely different place. In a really working application, this probability is quite low, but we want to foresee everything?
Decision:To verify the authenticity of the identifier you need to know the color of 4 pixels - top, bottom, right and left. If each of them is the same color or not completely transparent, the identifier is valid.
3. Images
In the case when we work with images, it is obvious that you need to create a certain image handler that iteratively processes its pixel data obtained via
getImageData
and replaces each opaque pixel with the color of the shape identifier, and each semi-transparent replaces it with a transparent one.
In this case, there is no clearly stated problem, there are only 3 nuances that are worth paying attention to.
- The first is to process the image, we need to create another Canvas element that is the same size as the image in order to output the processing function and use it as an image source in the background canvas. It is necessary either to delete this element after each use, or to create one common one and change its dimensions to fit each new picture.
- The second is the processing time of the image is proportional to its size, since it is necessary to bypass each pixel. Here, nothing can be said for sure, it all depends on the processing function.
- The third - in the case of images obtained from a different domain than the one on which the page containing the Canvas element is located, the protection of the browser will work. You have to handle the
SECURITY_ERR
error, and work with the image as with a rectangle.
Implementation
So, we already have a background canvas with the shapes drawn by the necessary colors. How to get the color of the pixel under the cursor? Below is an abstract code that demonstrates how to get the pixel below it, knowing the coordinates of the cursor:
Then, in any convenient way, you need to get a color from the channels, which can be compared with the figure ID.
It does not specifically describe the intricacies of the implementation of the mechanisms of events and the storage and processing of identifiers, since the options are countless.
A source
The source used is the following resource:
http://tschaub.net/blog/2011/03/31/canvas-hit-detection.htmlThe article does not claim the right to be a translation, but it uses most of the materials of the original resource.