Maison  >  Article  >  interface Web  >  Explication détaillée de l'exemple de code pour implémenter l'effet d'effacement de la gomme en HTML5 (image)

Explication détaillée de l'exemple de code pour implémenter l'effet d'effacement de la gomme en HTML5 (image)

黄舟
黄舟original
2017-03-20 16:05:302582parcourir

Cet effet vient d'être utilisé dans un projet récent, qui est un peu comme une carte à gratter Sur un appareil mobile, une certaine image est grattée. afficher une autre image. Les rendus sont les suivants :

Veuillez cliquer à droite pour DEMO : DEMO

Cette méthode est assez courante sur Internet. Au départ, je voulais trouver une démo en ligne et appliquer sa méthode. Après l'avoir appliquée, j'ai découvert qu'elle était bloquée sur Android. En raison des exigences des clients, Android ne nécessite aucune fonctionnalité particulière. au moins, on peut y jouer, mais la démo que j'ai trouvée en ligne est trop lente, ce qui rend impossible la lecture. Je voulais juste en écrire un moi-même, et cet article sert uniquement à enregistrer le processus de recherche.

La première chose qui me vient à l'esprit est d'utiliser le canevas HTML5 pour obtenir cet effet de grattage. Dans l'API du canevas, la méthode clearRect peut effacer les pixels, mais. La méthode clearRect efface la zone rectangulaire. Après tout, la gomme utilisée par la plupart des gens est ronde, donc la fonction puissante de la zone de découpage est introduite, qui est la méthode du clip. L'utilisation est très 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();

Le code ci-dessus réalise l'effacement d'une zone circulaire, c'est-à-dire implémente d'abord un chemin circulaire, puis ce chemin est utilisée comme zone de découpage, puis les pixels sont effacés. Une chose à noter est que vous devez d'abord enregistrer l'environnement Dessin. Après avoir effacé les pixels, vous devez réinitialiser l'environnement de dessin. Si vous ne le réinitialisez pas, les futurs dessins seront limités à ce découpage. zone.

Maintenant que l'effet d'effacement est là, il est maintenant temps d'écrire l'effet d'effacement du déplacement de la souris. Je vais utiliser la souris pour le décrire ci-dessous, car la version mobile est similaire. , remplacez simplement mousedown par touchstart. Remplacez simplement mousemove par touchmove, mouseup par touchend et obtenez les points de coordonnées de e.clientX à e.targetTouches[0].pageX.

Pour implémenter l'effacement du mouvement de la souris, j'ai juste pensé à effacer la zone circulaire de la position de la souris dans l'événement mousemove déclenché lorsque la souris bouge. Après l'avoir écrit, j'ai trouvé que. lorsque la souris bouge Lorsque la vitesse est très rapide, la zone effacée sera incohérente et l'effet suivant apparaîtra. Ce n'est évidemment pas l'effet de gomme que nous souhaitons.

 

Puisque tous les points sont incohérents, la prochaine chose à faire est de relier ces points Si cela est mis en œuvre Si. vous utilisez la fonction de dessin, vous pouvez connecter directement deux points via lineTo puis dessiner. Cependant, la zone de découpage dans l'effet d'effacement nécessite un chemin fermé. Si vous connectez simplement deux points, vous ne pouvez pas former une zone de découpage. Ensuite, j'ai pensé à utiliser des méthodes de calcul pour calculer les quatre coordonnées finales des rectangles dans les deux zones d'effacement, qui est le rectangle rouge dans l'image ci-dessous :

 

La méthode de calcul est également très simple, car on connaît les coordonnées des deux extrémités de la ligne reliant les deux zones de détourage, et on sait quelle largeur de ligne on veut, les coordonnées des quatre les extrémités du rectangle deviennent C'est facile à trouver, on a donc le code suivant :


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;

Les coordonnées des extrémités. De cette façon, la zone de découpage est un cercle plus un rectangle, et le code est organisé comme suit :

 hastouch = "ontouchstart"  
 window?:= hastouch?"touchstart":"mousedown"= 
 hastouch?"touchmove":"mousemove"= 
 hastouch?"touchend":"mouseup"= hastouch?e.targetTouches[0].pageX:e.clientX-= 
 hastouch?e.targetTouches[0].pageY:e.clientY-0,2*0,0= hastouch?e.targetTouches[0].pageX:e.clientX-= 
 hastouch?e.targetTouches[0].pageY:e.clientY- asin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));         
 acos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)))         
 x3 = x1+ y3 = y1- x4 = x1- y4 = y1+ x5 = x2+ y5 = y2- x6 = x2- y6 = y2+0,2*0,00,0==

 De cette façon, l'effet d'effacement de la souris est obtenu, mais il en reste un point à réaliser, c'est l'effet de la plupart des effacements. Lorsque vous effacez un certain nombre de pixels, tout le contenu de l'image sera automatiquement affiché. J'utilise imgData pour obtenir cet effet. Le code est le suivant :

var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=1){    
for(var y=0;y<imgData.height;y+=1){        
var i = (y*imgData.width + x)*4;        
if(imgData.data[i+3] > 0){
            dd++
        }
    }
}if(dd/(imgData.width*imgData.height)<0.4){
    canvas.className = "noOp";
}

Obtenez imgData, parcourez les pixels dans imgData, puis analysez l'alpha en rgba dans le tableau de données d'imgData, c'est-à-dire analysez la transparence , si le pixel est effacé, la transparence sera de 0, c'est-à-dire que le nombre de pixels avec une transparence non-0 dans le canevas actuel est comparé au nombre total de pixels sur le canevas si la proportion de pixels avec non-0. la transparence est inférieure à 40 %, cela signifie que plus de 60 % de la zone de la toile actuelle sera effacée et l'image pourra être automatiquement présentée.

  此处注意,我是把检查像素这段代码方法mouseup事件里面的,因为这个计算量相对来说还是不小,如果用户狂点鼠标,就会狂触发mouseup事件,也就是会疯狂的触发那个循环计算像素,计算量大到阻塞进程,导致界面卡住的情况,缓解办法如下:加个timeout,延迟执行像素计算,而在每一次点击的时候再清除timeout,也就是如果用户点击很快,这个计算也就触发不了了,还有一个提升的办法就是抽样检查,我上面的写法是逐个像素检查,逐个像素检查的话像素量太大,肯定会卡的,所以可以采用抽样检查,比如每隔30个像素检查一次,修改后的代码如下:

timeout = setTimeout(function(){    
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);    
var dd = 0;    
for(var x=0;x<imgData.width;x+=30){        
for(var y=0;y<imgData.height;y+=30){            
var i = (y*imgData.width + x)*4;            
if(imgData.data[i+3] >0){
                dd++
            }
        }
    }    if(dd/(imgData.width*imgData.height/900)<0.4){
        canvas.className = "noOp";
    }
},100)

  这样就可以较大限度的防止用户狂点击了,如果有其他更好的检查方法欢迎给出意见,谢谢。

  到了这一步就都写完了,然后就是测试的时候了,结果并不乐观,在android上还是卡啊卡啊,所以又得另想办法,最终发现了绘图环境中的globalCompositeOperation这个属性,这个属性的默认值是source-over,也就是,当你在已有像素上进行绘图时会叠加,但是还有一个属性是destination-out,官方解释就是:在源图像外显示目标图像。只有源图像外的目标图像部分才会被显示,源图像是透明的。好像不太好理解,但是其实自己测试一下就会发现很简单,也就是在已有像素的基础上进行绘图时,你绘制的区域里的已有像素都会被置为透明,直接看张图更容易理解:

    globalCompositeOperation属性效果图解。

  有了这个属性后,就意味着不需要用到clip,也就不需要用sin、cos什么的计算剪辑区域,直接用条粗线就行了,这样一来就能够很大限度的降低了计算量,同时减少了绘图环境API的调用,性能提升了,在android上运行应该也会流畅很多,下面是修改后的代码:


 hastouch = "ontouchstart"  window?:= hastouch?"touchstart":"mousedown"= hastouch?"touchmove":"mousemove"= hastouch?"touchend":"mouseup"= 
 hastouch?e.targetTouches[0].pageX:e.clientX-= hastouch?e.targetTouches[0].pageY:e.clientY-= "round"= "round"= a*2= "destination-out"1,0,2* 
 imgData = ctx.getImageData(0,0 dd = 0( x=0;x<imgData.width;x+=30( 
 y=0;y<imgData.height;y+=30 i = (y*imgData.width + x)*4(imgData.data[i+3] > 0++(dd/(imgData.width*imgData.height/900)<0.4){
                canvas.className = "noOp"= hastouch?e.targetTouches[0].pageX:e.clientX-= hastouch?e.targetTouches[0].pageY:e.clientY-==

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn