用HTML5 完成橡皮擦的涂抹实际效果的实例教程

日期:2021-02-24 类型:科技新闻 

关键词:小程序怎么做,凡科网小程序,微信小程序界面,线上报名小程序,微信小程序页面制作

近期新项目恰好用到这类实际效果,也便是有点像刮刮卡1样,在挪动机器设备上,把某张照片刮掉显示信息出另外一张照片。实际效果图以下:

DEMO请戳右:DEMO 
这类在网络上還是挺普遍的,原本就想立即在网上找个demo套用下他的方式就可以了,套用了才发现,在android上卡出翔了,由于顾客规定,在android不必求非常顺畅,最少要能玩,可是在网上找的那个demo确实太卡,压根便是无法玩的状况。因而就想自身写1个算了吧,本文也就权当纪录1下科学研究全过程。

  这类刮图的实际效果,最先想起便是用HTML5的canvas来完成,而canvas的API中,能够消除像素的便是clearRect方式,可是clearRect方式的消除地区矩形框,终究绝大多数人的习惯性中的橡皮擦全是圆形的,因此就引进了剪辑地区这个强劲的作用,也便是clip方式。用法很简易: 

XML/HTML Code拷贝內容到剪贴板
  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();   

  上面那段编码就完成了圆形地区的擦除,也便是先完成1个圆形相对路径,随后把这个相对路径做为剪辑地区,再消除像素就可以了。有个留意点便是必须先储存制图自然环境,消除完像素后要重设制图自然环境,假如不重设的话之后的制图全是会被限定在那个剪辑地区中。

  擦除实际效果有了,如今便是写电脑鼠标挪动擦除的实际效果了,下面我均用电脑鼠标来叙述,由于挪动端也类似,便是把mousedown换为touchstart,mousemove换为touchmove,mouseup换为touchend、和获得座标点由e.clientX换为e.targetTouches[0].pageX罢了。

  完成电脑鼠标挪动擦除,一开始便是想起电脑鼠标挪动时在开启的mousemove恶性事件中对电脑鼠标所属部位开展圆形地区擦除,写出来后发现,当电脑鼠标挪动速率很快的情况下,擦除的地区就不连贯性了,就会出現下面这类实际效果,这明显并不是大家要想的橡皮擦擦除实际效果。

既然全部点不连贯性,那接下来要做的事便是把这些点连贯性起来,假如是完成画图作用的话,便可以立即根据lineTo把两点之间联接起来再绘图,可是擦除实际效果中的剪辑地区规定如果闭合相对路径,假如是单纯性的把两个点连起来就没法产生剪辑地区了。随后我就想起用测算的方式,算出两个擦除地区中的矩形框4个节点座标来完成,也便是下图中的鲜红色矩形框:

测算方式也很简易,由于能够了解两个剪辑地区连线两个节点的座标,又了解大家要多宽的线条,矩形框的4个节点座标就变得非常容易求了,因此就有了下面的编码:
XML/HTML Code拷贝內容到剪贴板

  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和x2、y2便是两个节点,从而求出了4个节点的座标。这样1来,剪辑地区便是圈加矩形框,编码机构起来便是:
XML/HTML Code拷贝內容到剪贴板

  1. var hastouch = "ontouchstart" in window?true:false,//分辨是不是为挪动机器设备   
  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.   //电脑鼠标第1次点下的情况下擦除1个圆形地区,另外纪录第1个座标点   
  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.   //电脑鼠标挪动时开启该恶性事件   
  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.     //获得两个点之间的剪辑地区4个节点   
  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.     //确保线条的连贯性,因此在矩形框1端画圆   
  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.     //消除矩形框剪辑地区里的像素   
  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.     //纪录最终座标   
  63.         x1 = x2;   
  64.         y1 = y2;   
  65.     }   
  66. })  

  这般1来,电脑鼠标擦除的实际效果就完成了,但是也有1个要完成的点,便是绝大多数擦除的实际效果,当你擦了1定数量的像素后,就会全自动把全部照片內容展现出来,这个实际效果,我是用imgData来完成的。编码以下:
拷贝编码

XML/HTML Code拷贝內容到剪贴板
  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. }  

  获得到imgData,对imgData里的像素开展遍历,随后再对imgData的data数字能量数组里的rgba中的alpha开展剖析,也便是剖析全透明度,假如像素被擦除,那全透明度便是0了,也便是把当今画布中全透明度不为0的像素的数量跟画布总像素数开展较为,假如全透明度不为0 的像素数占比低于40%,那表明当今画布上就之后有百分610以上的地区被擦除,便可以全自动展现照片了。

  此处留意,我是把查验像素这段编码方式mouseup恶性事件里边的,由于这个测算量相对性来讲還是不小,假如客户狂点电脑鼠标,就会狂开启mouseup恶性事件,也便是会瘋狂的开启那个循环系统测算像素,测算量大到堵塞过程,致使页面卡住的状况,减缓方法以下:加个timeout,延迟时间实行像素测算,而在每次点一下的情况下再消除timeout,也便是假如客户点一下很快,这个测算也就开启不上了,也有1个提高的方法便是取样查验,我上面的写法是逐一像素查验,逐一像素查验的话像素量太大,毫无疑问会卡的,因此能够选用取样查验,例如每隔30个像素查验1次,改动后的编码以下:
拷贝编码

XML/HTML Code拷贝內容到剪贴板
  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)   

  这样便可以较大程度的避免客户狂点一下了,假如有别的更好的查验方式欢迎得出建议,感谢。

  到了这1步就都写完了,随后便是检测的情况下了,結果其实不开朗,在android上還是卡啊卡啊,因此又得另想方法,最后发现了制图自然环境中的globalCompositeOperation这个特性,这个特性的默认设置值是source-over,也便是,当你在已有像素勤奋行制图时会叠加,可是也有1个特性是destination-out,官方解释便是:在源图象外现示总体目标图象。仅有源图象外的总体目标图象一部分才会被显示信息,源图象是全透明的。仿佛不大好了解,可是实际上自身检测1下就会发现很简易,也便是在已有像素的基本勤奋行制图时,你绘图的地区里的已有像素都会被置为全透明,立即看张图更非常容易了解:

globalCompositeOperation特性实际效果图解。
有了这个特性后,就代表着不必须用到clip,也就不必须用sin、cos甚么的测算剪辑地区,立即用条粗线就可以了,这样1来就可以够很大程度的减少了测算量,另外降低了制图自然环境API的启用,特性提高了,在android上运作应当也会顺畅许多,下面是改动后的编码:


XML/HTML Code拷贝內容到剪贴板
  1. //根据改动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";  //设定线条两边为圆弧   
  16.         ctx.lineJoin = "round";  //设定线条转折点为圆弧   
  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. }   

  擦除那一部分编码就这么1点,也就非常于画图作用,立即设定line特性后根据lineTo开展绘图线条,要是事先把globalCompositeOperation设成destination-out,你所开展的1切绘图,都变为了擦除实际效果。电脑鼠标拖动开启的恶性事件里边编码也少了许多,制图目标的启用次数降低了,测算也降低了,特性提高大大滴。

  改好编码后就马上用自身的android机子检测了1下,果真这般,跟上1个相比,顺畅了许多,最少做到了顾客规定的能玩的程度了。

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