Disclaimer: This article is original article, if need reprint, please indicate source waxes, thank you!
Recently the project just used this effect, that is, a bit like a scratch card, on the mobile device, a picture is scraped off to show another picture. As follows:
Demo Please poke right:demo
This is quite common on the Internet, originally wanted to directly online to find a demo to apply his method on the line, applied to find, in the Android card out of Cheung, because the customer requirements, Android does not require special fluency, at least to be able to play, but the online look for the demo is too card, It's simply a situation where you can't play. So I want to write a forget, this article also on the right when recording the research process.
The effect of this scraping, the first thought is to use HTML5 canvas to achieve, and the canvas API, you can clear pixels is the Clearrect method, but the Clearrect method of the clear area of the rectangle, after all, most people's habit of the eraser is round, So it introduces the powerful function of clipping region, which is the clip method. The usage is simple:
Ctx.save () Ctx.beginpath () Ctx.arc (x2,y2,a,0,2*Math.PI); Ctx.clip () ctx.clearrect (0,0, Canvas.width,canvas.height); Ctx.restore ();
The above code realizes the erase of the circle area, that is, to implement a circular path, then use this path as the clipping region, then clear the pixels. One thing to note is that you need to save the drawing environment first, to reset the drawing environment after you have cleared the pixels, and if you do not reset, future drawings will be limited to that clipping area.
Erase effect has, now is to write the effect of the mouse move erase, the following I use the mouse to describe, because the mobile side is similar, is to replace the MouseDown Touchstart,mousemove replaced with Touchmove,mouseup touchend, and get coordinate points changed from E.clientx to E.targettouches[0].pagex.
The realization of mouse movement erase, just at the beginning is to think of the mouse movement in the MouseMove event triggered by the mouse in the location of the circular area to erase, write out found that when the mouse movement speed, the erase area is not coherent, will appear the following effect, This is obviously not the eraser effect we want to erase.
Since there is a bit incoherent, the next thing to do is to make these points coherent, if it is to achieve the drawing function, you can directly through the LineTo to connect between two points and then draw, but the erase effect of the clipping region is required to close the path, if it is simple to connect two points can not form a clipping region. Then I think of the calculation method, calculate the two erase area of the rectangle four vertex coordinates to achieve, that is, the red rectangle in:
The calculation method is also very simple, because you can know the two clipping area line two endpoints of the coordinates, but also know that we want to how wide lines, the rectangle's four endpoint coordinates becomes easy to request, so there is the following code:
var asin = A*math.sin (Math.atan (y2-y1)/(X2-X1)); var acos = A*math.cos (Math.atan (y2-y1)/(X2-X1))var x3 = x1+asin; var y3 = y1-ACOs; var x4 = x1-asin; var y4 = y1+ACOs; var x5 = x2+asin; var y5 = y2-ACOs; var x6 = x2-asin; var y6 = Y2+acos;
x1, y1, and x2, and Y2 are two endpoints, thus finding the coordinates of the four endpoints. In this way, the clipping area is the circle and the rectangle, and the code is organized:
varHastouch = "Ontouchstart"inchWindow?true:false,//Determine if the mobile device is Tapstart= Hastouch? " Touchstart ":" MouseDown ", Tapmove= Hastouch? " Touchmove ":" MouseMove ", Tapend= Hastouch? " Touchend ":" MouseUp "; Canvas.addeventlistener (Tapstart,function(e) {e.preventdefault (); X1= hastouch?e.targettouches[0].pagex:e.clientx-Canvas.offsetleft; Y1= hastouch?e.targettouches[0].pagey:e.clienty-Canvas.offsettop;
Erase a circular area at the first point of the mouse and record the first coordinate point ctx.save () Ctx.beginpath () Ctx.arc (X1,y1,a,0,2*Math.PI); Ctx.clip () Ctx.clearrect (0,0, Canvas.width,canvas.height); Ctx.restore (); Canvas.addeventlistener (Tapmove, Tapmovehandler); Canvas.addeventlistener (Tapend,function() {Canvas.removeeventlistener (tapmove, tapmovehandler); });
This event is triggered when the mouse movesfunctionTapmovehandler (e) {e.preventdefault () X2= hastouch?e.targettouches[0].pagex:e.clientx-Canvas.offsetleft; Y2= hastouch?e.targettouches[0].pagey:e.clienty-Canvas.offsettop;
Gets the clipping region between two points four endpointsvarASIN = A*math.sin (Math.atan (y2-y1)/(X2-X1));varACOs = A*math.cos (Math.atan (y2-y1)/(X2-X1))varx3 = x1+ASIN; varY3 = y1-ACOs; varx4 = x1-ASIN; varY4 = y1+ACOs; varx5 = x2+ASIN; varY5 = y2-ACOs; varx6 = x2-ASIN; varY6 = y2+ACOs;
Ensure line coherence, so draw round Ctx.save () Ctx.beginpath () Ctx.arc at one end of the rectangle (x2,y2,a,0,2*Math.PI); Ctx.clip () Ctx.clearrect (0,0, canvas.width,canvas.height); Ctx.restore ();
Clears pixels in the rectangular clipping area ctx.save () Ctx.beginpath () Ctx.moveto (X3,Y3); Ctx.lineto (X5,Y5); Ctx.lineto (X6,Y6); Ctx.lineto (X4,Y4); Ctx.closepath (); Ctx.clip () Ctx.clearrect (0,0, Canvas.width,canvas.height); Ctx.restore ();
Record the last coordinate X1=x2; Y1=Y2; }})
As a result, the effect of the mouse erase is realized, but there is a point to achieve, that is, most of the effect of erasing, when you wipe a certain number of pixels, will automatically put all the picture content, this effect, I use imgdata to achieve. The code is as follows:
var imgdata = ctx.getimagedata (0,0 var dd = 0; for (var x=0;x Span style= "color: #0000ff;" >for (var y=0;y { var i = (y*imgdata.width + x) *4; if (imgdata.data[i+3] > 0) {dd
++ if (dd/(Imgdata.width*imgda Ta.height) <0.4) {canvas.classname = "noOp" ;}
Get to Imgdata, the pixels in the imgdata to traverse, and then the Imgdata data array in the RGBA of the Alpha analysis, that is, the analysis of transparency, if the pixel is erased, the transparency is 0, That is, the opacity of the current canvas is not 0 of pixels compared with the total number of pixel on the canvas, if the opacity is not 0 pixels in proportion to less than 40%, it means that the current canvas on the future has more than 60 of the area is erased, you can automatically render the picture.
Note here, I am the check pixel This code method MouseUp event inside, because this calculation amount is relatively small, if the user Mad Mouse, will be crazy trigger MouseUp event, that will let the browser crashes, the mitigation is as follows: Add a timeout, delay the execution of pixel calculation, And in every click and then clear timeout, that is, if the user click quickly, this calculation will not trigger, there is a way to improve is the sampling check, I above the method is a pixel-by-bit inspection, pixel-by-page inspection, pixel volume is too large, will certainly card, so you can use sampling check, For example, check every 5 pixels, the modified code is as follows:
Timeout = SetTimeout (function(){ varImgdata = Ctx.getimagedata (0,0, Canvas.width,canvas.height); varDD = 0; for(varX=0;x){ for(varY=0;y){ vari = (y*imgdata.width + x); if(Imgdata.data[i+3] >0) {dd++ } } } if(dd/(IMGDATA.WIDTH*IMGDATA.HEIGHT/25) <0.4) {Canvas.classname= "NoOp"; }},800)
This can be a great limit to prevent users to click, if there are other better inspection methods welcome to give advice, thank you.
To this step is finished, and then is the test time, the results are not optimistic, on Android or card Ah, so we have to find another way, finally found in the drawing environment globalcompositeoperation this attribute, The default value of this property is Source-over, that is, when you draw on an existing pixel overlay, but there is also a property is Destination-out, the official explanation is: in the source image affair City target image. Only the part of the target image that is outside the source image is displayed, and the source image is transparent. It seems not very good to understand, but in fact, the test will find it very simple, that is, on the basis of the existing pixels in the drawing, the area you draw the existing pixels will be set to transparent, directly to see the picture is easier to understand:
The Globalcompositeoperation property solution.
With this attribute, it means that you do not need to use clip, you do not need to use sin, cos what the calculation of the clipping region, directly with a thick line line, which can greatly reduce the computational volume, while reducing the drawing environment API calls, performance improved, Running on Android should also be a lot smoother, here is the modified code:
//to achieve the erase effect by modifying the GlobalcompositeoperationfunctionTapclip () {varHastouch = "Ontouchstart"inchWindow?true:false, Tapstart= Hastouch? " Touchstart ":" MouseDown ", Tapmove= Hastouch? " Touchmove ":" MouseMove ", Tapend= Hastouch? " Touchend ":" MouseUp "; Canvas.addeventlistener (Tapstart,function(e) {
Cleartimeout (timeout) e.preventdefault (); X1= hastouch?e.targettouches[0].pagex:e.clientx-Canvas.offsetleft; Y1= hastouch?e.targettouches[0].pagey:e.clienty-Canvas.offsettop; Ctx.linecap= "Round"; Set line ends as Arc ctx.linejoin= "Round"; Set line jog to arc ctx.linewidth= A*2; Ctx.globalcompositeoperation= "Destination-out"; Ctx.save (); Ctx.beginpath () Ctx.arc (X1,y1,1,0,2*Math.PI); Ctx.fill (); Ctx.restore (); Canvas.addeventlistener (Tapmove, Tapmovehandler); Canvas.addeventlistener (Tapend,function() {Canvas.removeeventlistener (tapmove, Tapmovehandler);
Timeout = setTimeout (function () {varImgdata = Ctx.getimagedata (0,0, Canvas.width,canvas.height); varDD = 0; for(varX=0;x){ for(varY=0;y){ vari = (y*imgdata.width + x); if(Imgdata.data[i+3] > 0) {dd++ } } } if(dd/(IMGDATA.WIDTH*IMGDATA.HEIGHT/25) <0.4) {canvas.classname = "noOp"; }
},800)}); functionTapmovehandler (e) {e.preventdefault () X2= hastouch?e.targettouches[0].pagex:e.clientx-Canvas.offsetleft; Y2= hastouch?e.targettouches[0].pagey:e.clienty-Canvas.offsettop; Ctx.save (); Ctx.moveto (X1,Y1); Ctx.lineto (X2,Y2); Ctx.stroke (); Ctx.restore () x1=x2; Y1=Y2; } })}
Erase that part of the code, that is, the equivalent of drawing function, directly set the line property through the LineTo to draw lines, as long as the globalcompositeoperation set to Destination-out, you do all the drawing, has become the erase effect. Mouse sliding triggered events inside the code is also a lot less, the number of calls to the drawing object is reduced, the calculation is reduced, the performance increase greatly drops.
Change the code immediately with their own Android test a bit, as it did, with a more fluent, at least to meet the customer requirements to play the point.