ホームページ > 記事 > ウェブフロントエンド > HTML5で消しゴムの消去効果を実装するサンプルコードを詳しく解説(写真)
最近のプロジェクトでは、モバイル デバイス上で特定の 画像 をスクラッチして別の画像を表示する、スクラッチ カードに似た効果を使用しました。レンダリングは次のとおりです:
デモについては右をクリックしてください: DEMO
この種のことはインターネット上で非常に一般的であり、最初はオンラインでデモを見つけて適用したいと考えていました。このとき初めて、顧客の要件により、ゲームが Android で動作しなくなっていることに気づきました。ただし、オンラインで見つけたデモは、少なくとも Android でプレイできる必要がありました。ラグがありすぎて全くプレイできませんでした。なので、自分でも書いてみたかっただけで、この記事は研究過程を記録するためのものです。
このスクレイピング効果を実現するには、HTML5 キャンバスを使用することが最初に思いつきます。キャンバス API では、clearRect メソッドでピクセルをクリアできますが、結局のところ、clearRect メソッドは領域の四角形をクリアします。従来の消しゴムは丸いので、クリッピングエリアの強力な機能であるクリップメソッドが導入されました。使い方は非常に簡単です:
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();
上記のコードは、円形領域の消去を実現します。つまり、最初に円形パスを実装し、次にこのパスをクリッピング領域として使用し、その後ピクセルをクリアします。注意すべき点は、まず drawing 環境を保存する必要があることです。ピクセルをクリアした後、描画環境をリセットする必要があります。リセットしないと、今後の描画はそのクリッピング領域に制限されます。
消去効果ができたので、今度はマウスの移動による消去効果を記述します。モバイル版でも同様なので、mousedown を touchstart に置き換え、mousemove を置き換えるだけです。 touchmove、mouseupでのタッチエンドと座標点の取得をe.clientXからe.targetTouches[0].pageXに変更します。
マウスの動きの消去を実現するために、マウスが動いたときにトリガーされたmousemoveイベントでマウスの位置の円形の領域を消去することを考えました。書き出してみると、マウスが非常に速く動くと、消去が発生し、領域は一貫性を失い、次のような効果が現れます。これは明らかに私たちが望んでいる消しゴム効果ではありません。
すべての点が支離滅裂なので、次に行うことはこれらの点を接続することです。描画機能を実装している場合は、 lineTo を介して 2 つの点を直接接続してから描画することもできます。消去エフェクトのクリッピング エリアには閉じたパスが必要です。2 点を接続するだけではクリッピング エリアを形成できません。そこで、下の図の赤い四角形である、2 つの消去領域内の四角形の 4 つの端点座標を計算する計算方法を使用することを考えました。
計算方法も非常に簡単で、 2 つのクリッピング領域を結ぶ線の 2 つの端点の座標と必要な線の幅がわかったので、長方形の 4 つの端点の座標を見つけるのが簡単になるため、次のコードができます。
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とx2,y2が2つの端点なので、4つの端点の座標が得られます。このようにして、クリッピング領域は円と長方形になり、コードは次のように構成されます:
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==
このようにして、マウスの消去効果が得られますが、まだ達成すべき点が 1 つあります。ほとんどの消去の効果 特定の数のピクセルを消去すると、すべての画像コンテンツが自動的に表示されます。この効果を実現するには、imgData を使用します。コードは以下の通りです:
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"; }
imgDataを取得し、imgData内のピクセルを走査し、imgDataのデータ配列内のrgbaのアルファを解析、つまりピクセルが消去されていれば透明度を解析します。が 0、つまり、現在のキャンバス内の透明度が 0 以外のピクセルの数と、キャンバス内の透明度が 0 以外のピクセルの割合が 40% 未満であることを意味します。現在のキャンバスの 100% ピクセルになります。60 を超える領域が消去されると、画像が自動的に表示されます。
此处注意,我是把检查像素这段代码方法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机子测试了一下,果然如此,跟上一个相比,流畅了很多,至少达到了客户要求的能玩的地步了。
以上がHTML5で消しゴムの消去効果を実装するサンプルコードを詳しく解説(写真)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。