Home >Web Front-end >H5 Tutorial >Tutorial on using HTML5 to achieve the eraser smearing effect_html5 tutorial skills

Tutorial on using HTML5 to achieve the eraser smearing effect_html5 tutorial skills

WBOY
WBOYOriginal
2016-05-16 15:46:372222browse

This effect has just been used in a recent project, which is a bit like a scratch card. On a mobile device, a picture is scratched off to display another picture. The rendering is as follows:
2015511163305472.png (974×840)

DEMO please click on the right: DEMO
This is quite common on the Internet. I originally wanted to just find a demo online and apply his method. After applying it, I found out that in android Because of customer requirements, it is not required to be particularly smooth on Android, at least it must be playable, but the demo I found online is too laggy and cannot be played at all. So I just wanted to write one myself, and this article is just for recording the research process.

The first thing that comes to mind for this scraping effect is to use the HTML5 canvas. In the canvas API, the clearRect method that can clear pixels is the clearRect method, but the clearRect method clears the area rectangle. After all, most people are used to it. Erasers are all round, so the powerful function of the clipping area is introduced, which is the clip method. Usage is very simple:

XML/HTML CodeCopy content 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 a circular area, that is, first implements a circular path, and then uses this path as the clipping area, and then clears the pixels. One thing to note is that you need to save the drawing environment first, and reset the drawing environment after clearing the pixels. If you do not reset, future drawings will be limited to that clipping area.

Now that the erasing effect is there, now it’s time to write the erasing effect of mouse movement. I’ll use the mouse to describe it below, because the mobile version is similar, that is, replace mousedown with touchstart, mousemove with touchmove, and mouseup with touchend. , and the coordinate point acquisition is changed from e.clientX to e.targetTouches[0].pageX.

To implement mouse movement erasure, I first thought of erasing the circular area where the mouse is located in the mousemove event triggered when the mouse moves. After writing it out, I found that when the mouse moves very fast, the erased The area is no longer coherent, and the following effect will appear. This is obviously not the eraser effect we want.
2015511163949198.jpg (1103×693)

Since all the points are incoherent, the next thing to do is to connect these points. If you are implementing the drawing function, you can directly connect the two points through lineTo and then draw, but the erasure effect is The clipping area requires a closed path. If you simply connect two points, the clipping area cannot be formed. Then I thought of using calculation methods to calculate the four endpoint coordinates of the rectangles in the two erasing areas, which is the red rectangle in the picture below:
2015511164105508.png (343×129)

The calculation method is also very simple, because we can know the coordinates of the two endpoints of the line connecting the two clipping areas, and also know how wide the line we want, the coordinates of the four endpoints of the rectangle become easy to find, so there is Code below:
XML/HTML CodeCopy content 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, thus the coordinates of the four endpoints are obtained. In this way, the clipping area is a circle plus a rectangle, and the code is organized as follows:
XML/HTML CodeCopy the content to the clipboard

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

In this way, the mouse erasing effect is achieved, but there is another point to be achieved, which is the effect of most erasing. When you erase a certain number of pixels, all the image content will be automatically Shown, this effect, I use imgData to achieve. The code is as follows:
Copy code

XML/HTML CodeCopy content 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 dd
  8.                                                               
  9. }
  10. }
  11. if(dd/(imgData.width*imgData.height)
  12. <0.4){
  13. canvas.className = "noOp";
  14. }

Obtain 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 pixel is erased, the transparency will be 0, that is It is to compare the number of pixels with non-zero transparency in the current canvas to the total number of pixels on the canvas. If the proportion of pixels with non-zero transparency is less than 40%, it means that there will be more than 60% of the area on the current canvas. Once erased, the picture can be automatically presented.

Note here that I put the code for checking pixels 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, which means he will be crazy. Triggering that loop to calculate pixels, the amount of calculation is so large that it blocks the process and causes the interface to get stuck. The solution is as follows: add a timeout to delay the execution of the pixel calculation, and clear the timeout every time the user clicks, that is, if the user clicks for a long time Quickly, this calculation will no longer be triggered. Another way to improve is to do random inspections. The way I wrote it above is to check pixel by pixel. If the number of pixels is too large, it will definitely get stuck, so you can use random inspection, such as Check every 30 pixels. The modified code is as follows:
Copy code

XML/HTML CodeCopy content 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 dd
  9.                                                                 
  10.                                                               
  11. }
  12. if(dd/(imgData.width*imgData.height/900)
  13. <
  14. 0.4){
  15. canvas.className
  16. = "noOp"; }
  17. },100)
  18. This can prevent users from clicking wildly to the greatest extent. If there are other better checking methods, please give your opinions, thank you.

    At this step, everything was written, and then it was time to test. The results were not optimistic. It was still stuck on android, so I had to think of another way. Finally, I discovered the globalCompositeOperation attribute in the drawing environment. The default value of this property is source-over, that is, it will be overlaid when you draw on existing pixels, but there is also a property called destination-out. The official explanation is: display the destination image outside the source image. Only the portion of the target image outside the source image will be displayed, and the source image is transparent. It seems hard to understand, but in fact, if you test it yourself, you will find that it is very simple. That is, when you draw based on existing pixels, the existing pixels in the area you draw will be made transparent. You can change it directly by looking at the picture. Easy to understand:
    2015511164219714.jpg (553×390)

    Illustration of the effect of globalCompositeOperation attribute.
    With this attribute, it means that there is no need to use clip, and there is no need to use sin or cos to calculate the clipping area. Just use a thick line, which can greatly reduce the cost. The amount of calculation is reduced, and the calls to the drawing environment API are reduced. The performance is improved, and running on Android should be much smoother. The following is the modified code:


    XML/HTML CodeCopy content to clipboard
    1. //Achieve erasure effect by modifying 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 as arcs
    16.  ctx.lineJoin = "round"; //Set the 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.    
    31. timeout
    32. = setTimeout(function(){
    33.             var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);   
    34.             var dd = 0;   
    35.             for(var x=0;x<imgData.width;x =30){   
    36.                 for(var y=0;y<imgData.height;y =30){   
    37.                       var i = (y*imgData.width   x)*4;   
    38.                     if(imgData.data[i 3] > 0){   
    39.                         dd   
    40.                     }   
    41.                 }   
    42.             }   
    43.             if(dd/(imgData.width*imgData.height/900)<0.4){   
    44.                 canvas.className = "noOp";   
    45.             }   
    46.        },100)   
    47.         });   
    48.         function tapmoveHandler(e){   
    49.             e.preventDefault()   
    50.             x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;   
    51.             y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop;   
    52.                
    53.             ctx.save();   
    54.             ctx.moveTo(x1,y1);   
    55.             ctx.lineTo(x2,y2);   
    56.             ctx.stroke();   
    57.             ctx.restore()   
    58.                
    59.             x1 = x2;   
    60.             y1 = y2;   
    61.         }   
    62.     })   
    63. }   

      擦除那部分代码就这么一点,也就相当于画图功能,直接设置line属性后通过lineTo进行绘制线条,只要事前把globalCompositeOperation设成destination-out,你所进行的一切绘制,都变成了擦除效果。鼠标滑动触发的事件里面代码也少了很多,绘图对象的调用次数减少了,计算也减少了,性能提升大大滴。

      改好代码后就立即用自己的android机子测试了一下,果然如此,跟上一个相比,流畅了很多,至少达到了客户要求的能玩的地步了。

      源码地址:https://github.com/whxaxes/canvas-test/blob/gh-pages/src/Funny-demo/clip/clip.html

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn