Using HTML5 to achieve the application effect of eraser tutorial

Time:2021-12-29

This effect is just used in the recent project, which is a bit like a scratch card. On the mobile device, scrape off one picture and display another picture. The renderings are as follows:
2015511163305472.png (974×840)

Demo please stamp right:DEMO 
This is too laggy on the Internet. Originally, I wanted to find a demo online to find a way to use his method, and found it was stuck on the Android card. Because the customer request, Android didn’t need to be particularly fluent, at least to play, but the demo that was online was too stuck. So I want to write one by myself. This paper has the right to record the research process.

The first thought of this scraping effect is to use the canvas of HTML5. In the canvas API, the clearrect method can clear pixels, but the clearrect method can clear the rectangle of the area. After all, most people’s custom erasers are circular, so it introduces the powerful function of the clipping area, that is, the clip method. The usage is simple:

XML/HTML CodeCopy contents to clipboard
  1. ctx.save()   
  2. ctx.beginPath()   
  3. ctx.arc(x2,y2,a,0,2*Math.PI);   
  4. ctx.clip()   
  5. ctx.clearRect(0,0,canvas.width,canvas.height);   
  6. ctx.restore();   

The above code realizes the erasure of the circular area, that is, first implement a circular path, then take this path as the clipping area, and then clear the pixels. One thing to note is that you need to save the drawing environment first. After clearing the pixels, you need to reset the drawing environment. If you don’t reset, future drawings will be limited to that clipping area.

Now that the erasure effect is available, I will describe it with the mouse, because the mobile end is similar, that is, replace MouseDown with touchstart, MouseMove with touchmove, mouseup with touchend, and obtain coordinate points from e.clientx to e.targettouches [0] Just pagex.

To realize the mouse movement erasure, at the beginning, I thought of erasing the circular area where the mouse is located in the triggered MouseMove event when the mouse moves. After writing it out, I found that when the mouse moves very fast, the erased area is discontinuous, and the following effect will appear. Obviously, this is not the eraser erasure effect we want.
2015511163949198.jpg (1103×693)

Since all points are incoherent, the next thing to do is to connect these points. If the drawing function is realized, you can connect the two points directly through lineto and draw them. However, the clipping area in the erasure effect requires a closed path. If the two points are simply connected, the clipping area cannot be formed. Then I thought of using the calculation method to calculate the four endpoint coordinates of the rectangle in the two erasure areas, that is, the red rectangle in the figure below:
2015511164105508.png (343×129)

The calculation method is also very simple, because you can know the coordinates of the two endpoints connecting the two clipping areas and how wide the line we want, and the coordinates of the four endpoints of the rectangle become easy to find, so you have the following code:
XML/HTML CodeCopy contents to clipboard

  1. var aasin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));   
  2. var aacos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)))   
  3. var x3 = x1+asin;   
  4. var y3 = y1-acos;   
  5. var x4 = x1-asin;   
  6. var y4 = y1+acos;   
  7. var x5 = x2+asin;   
  8. var y5 = y2-acos;   
  9. var x6 = x2-asin;   
  10. var y6 = y2+acos;   

X1, Y1 and X2, Y2 are the two endpoints, so the coordinates of the four endpoints are obtained. In this way, the clipping area is a circle and rectangle, and the code is organized as follows:
XML/HTML CodeCopy contents to clipboard

  1. var hastouch = “ontouchstart”in window? True: false, / / judge whether it is a mobile device
  2.     tapstart = hastouch?”touchstart”:”mousedown”,   
  3.     tapmove = hastouch?”touchmove”:”mousemove”,   
  4.     tapend = hastouch?”touchend”:”mouseup”;   
  5.   
  6. canvas.addEventListener(tapstart , function(e){   
  7.     e.preventDefault();   
  8.        
  9.     x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;   
  10.     y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop;   
  11.        
  12. //When the mouse clicks for the first time, erase a circular area and record the first coordinate point at the same time
  13.     ctx.save()   
  14.     ctx.beginPath()   
  15.     ctx.arc(x1,y1,a,0,2*Math.PI);   
  16.     ctx.clip()   
  17.     ctx.clearRect(0,0,canvas.width,canvas.height);   
  18.     ctx.restore();   
  19.        
  20.     canvas.addEventListener(tapmove , tapmoveHandler);   
  21.     canvas.addEventListener(tapend , function(){   
  22.         canvas.removeEventListener(tapmove , tapmoveHandler);   
  23.     });   
  24. //This event is triggered when the mouse moves
  25.     function tapmoveHandler(e){   
  26.         e.preventDefault()   
  27.         x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;   
  28.         y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop;   
  29.            
  30. //Gets the four endpoints of the clip area between two points
  31.         var aasin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));   
  32.         var aacos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)))   
  33.         var x3 = x1+asin;   
  34.         var y3 = y1-acos;   
  35.         var x4 = x1-asin;   
  36.         var y4 = y1+acos;   
  37.         var x5 = x2+asin;   
  38.         var y5 = y2-acos;   
  39.         var x6 = x2-asin;   
  40.         var y6 = y2+acos;   
  41.            
  42. //Ensure the continuity of lines, so draw a circle at one end of the rectangle
  43.         ctx.save()   
  44.         ctx.beginPath()   
  45.         ctx.arc(x2,y2,a,0,2*Math.PI);   
  46.         ctx.clip()   
  47.         ctx.clearRect(0,0,canvas.width,canvas.height);   
  48.         ctx.restore();   
  49.        
  50. //Clears the pixels in the rectangular clipping area
  51.         ctx.save()   
  52.         ctx.beginPath()   
  53.         ctx.moveTo(x3,y3);   
  54.         ctx.lineTo(x5,y5);   
  55.         ctx.lineTo(x6,y6);   
  56.         ctx.lineTo(x4,y4);   
  57.         ctx.closePath();   
  58.         ctx.clip()   
  59.         ctx.clearRect(0,0,canvas.width,canvas.height);   
  60.         ctx.restore();   
  61.            
  62. //Record last coordinates
  63.         x1 = x2;   
  64.         y1 = y2;   
  65.     }   
  66. })  

In this way, the effect of mouse erasure is realized, but there is another point to be realized, that is, most of the erasure effect. When you erase a certain number of pixels, all picture contents will be automatically presented. I use imgdata to achieve this effect. The code is as follows:
Copy code

XML/HTML CodeCopy contents to clipboard
  1. var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);   
  2. var dd = 0;   
  3. for(var x=0;x<imgData.width;x+=1){   
  4.     for(var y=0;y<imgData.height;y+=1){   
  5.         var i = (y*imgData.width + x)*4;   
  6.         if(imgData.data[i+3] > 0){   
  7.             dd++   
  8.         }   
  9.     }   
  10. }   
  11. if(dd/(imgData.width*imgData.height)<0.4){   
  12.     canvas.className = “noOp”;   
  13. }  

Get imgdata, traverse the pixels in imgdata, and then analyze the alpha in RGBA in the data array of imgdata, that is, analyze the transparency. If the pixels are erased, the transparency is 0, that is, compare the number of pixels in the current canvas with the total number of pixels in the canvas, If the proportion of pixels whose transparency is not 0 is less than 40%, it means that more than 60% of the area on the current canvas has been erased in the future, and the picture can be presented automatically.

Note here that the code of checking pixels is included in the mouseup event, because the amount of calculation is relatively large. If the user clicks the mouse wildly, the mouseup event will be triggered wildly, that is, the cyclic calculation pixel will be triggered wildly. The amount of calculation is so large that the process is blocked, resulting in the interface jamming. The mitigation measures are as follows: add a timeout, Delay the pixel calculation and clear the timeout at each click, that is, if the user clicks quickly, the calculation will not be triggered. Another way to improve is sampling inspection. My above writing method is to check pixel by pixel. If the pixel is checked pixel by pixel, it will be stuck. Therefore, sampling inspection can be used, For example, check every 30 pixels. The modified code is as follows:
Copy code

XML/HTML CodeCopy contents to clipboard
  1. timeout = setTimeout(function(){   
  2.     var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);   
  3.     var dd = 0;   
  4.     for(var x=0;x<imgData.width;x+=30){   
  5.         for(var y=0;y<imgData.height;y+=30){   
  6.             var i = (y*imgData.width + x)*4;   
  7.             if(imgData.data[i+3] >0){   
  8.                 dd++   
  9.             }   
  10.         }   
  11.     }   
  12.     if(dd/(imgData.width*imgData.height/900)<0.4){   
  13.         canvas.className = “noOp”;   
  14.     }   
  15. },100)   

In this way, users can be prevented from clicking wildly. If there are other better inspection methods, you are welcome to give comments. Thank you.

At this stage, we have finished writing, and then it’s time to test. The results are not optimistic. There are still cards and cards on Android, so we have to find another way. Finally, we found the attribute globalcompositeoperation in the drawing environment. The default value of this attribute is source over, that is, it will be superimposed when you draw on existing pixels, However, another attribute is destination out. The official explanation is to display the target image outside the source image. Only the target image outside the source image will be displayed, and the source image is transparent. It doesn’t seem easy to understand, but in fact, if you test it yourself, you will find that it is very simple, that is, when you draw on the basis of existing pixels, the existing pixels in the area you draw will be made transparent, so it’s easier to understand by looking at the picture directly:
2015511164219714.jpg (553×390)

Illustration of the effect of the globalcompositeoperation property.
With this attribute, you don’t need to use clip, sin and COS to calculate the clipping area. Just use a thick line. In this way, you can greatly reduce the amount of calculation, reduce the call of drawing environment API, improve the performance, and run much more smoothly on Android. The following is the modified code:

XML/HTML CodeCopy contents to clipboard
  1. //The effect of erasure is achieved by modifying the globalcompositeoperation
  2. function tapClip(){   
  3.     var hastouch = “ontouchstart” in window?true:false,   
  4.         tapstart = hastouch?”touchstart”:”mousedown”,   
  5.         tapmove = hastouch?”touchmove”:”mousemove”,   
  6.         tapend = hastouch?”touchend”:”mouseup”;   
  7.        
  8.     canvas.addEventListener(tapstart , function(e){   
  9.      clearTimeout(timeout)   
  10.         e.preventDefault();   
  11.            
  12.         x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;   
  13.         y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop;   
  14.            
  15.         ctx.lineCap = “round”; / / set both ends of the line to arc
  16.         ctx.lineJoin = “round”; / / set line turning to arc
  17.         ctx.lineWidth = a*2;     
  18.         ctx.globalCompositeOperation = “destination-out”;   
  19.            
  20.         ctx.save();   
  21.         ctx.beginPath()   
  22.         ctx.arc(x1,y1,1,0,2*Math.PI);   
  23.         ctx.fill();   
  24.         ctx.restore();   
  25.            
  26.         canvas.addEventListener(tapmove , tapmoveHandler);   
  27.         canvas.addEventListener(tapend , function(){   
  28.             canvas.removeEventListener(tapmove , tapmoveHandler);   
  29.                
  30.        timeout = setTimeout(function(){   
  31.             var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);   
  32.             var dd = 0;   
  33.             for(var x=0;x<imgData.width;x+=30){   
  34.                 for(var y=0;y<imgData.height;y+=30){   
  35.                     var i = (y*imgData.width + x)*4;   
  36.                     if(imgData.data[i+3] > 0){   
  37.                         dd++   
  38.                     }   
  39.                 }   
  40.             }   
  41.             if(dd/(imgData.width*imgData.height/900)<0.4){   
  42.                 canvas.className = “noOp”;   
  43.             }   
  44.        },100)   
  45.         });   
  46.         function tapmoveHandler(e){   
  47.             e.preventDefault()   
  48.             x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;   
  49.             y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop;   
  50.                
  51.             ctx.save();   
  52.             ctx.moveTo(x1,y1);   
  53.             ctx.lineTo(x2,y2);   
  54.             ctx.stroke();   
  55.             ctx.restore()   
  56.                
  57.             x1 = x2;   
  58.             y1 = y2;   
  59.         }   
  60.     })   
  61. }   

Erasing that part of the code is like this, which is equivalent to the drawing function. After directly setting the line attribute, draw the lines through lineto. As long as you set the globalcompositeoperation to destination out in advance, all your drawing will become the erasing effect. The code in the event triggered by mouse sliding is also much less, the number of calls to drawing objects is reduced, the calculation is also reduced, and the performance is greatly improved.

After changing the code, I immediately tested it with my own Android machine. However, compared with the previous one, it was much smoother, at least up to the point of being able to play as required by the customer.

Source address:https://github.com/whxaxes/canvas-test/blob/gh-pages/src/Funny-demo/clip/clip.html