このエフェクトは最近のプロジェクトで使用されたばかりで、モバイル デバイス上で画像をスクラッチして別の画像を表示するものに似ています。レンダリングは次のとおりです:
デモは右をクリックしてください: デモ これはインターネット上で非常に一般的ですが、最初はオンラインでデモを見つけて、彼の方法を適用したかっただけです。顧客の要求により、Android 上で特にスムーズである必要はなく、少なくともプレイ可能である必要がありますが、オンラインで見つけたデモはラグがあり、まったくプレイできませんでした。なので、自分でも書いてみたかっただけで、この記事は研究過程を記録するためのものです。
このスクレイピング効果でまず思い浮かぶのは、キャンバスAPIでピクセルをクリアできるclearRectメソッドですが、結局のところclearRectメソッドは領域四角形をクリアします。消しゴムは丸いものに慣れている人が多いので、クリッピング エリアの強力な機能であるクリップ メソッドを紹介します。使用方法は非常に簡単です:
XML/HTML コード コンテンツをクリップボードにコピー
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();
上記のコードは円形領域の消去を実現しています。つまり、最初に円形パスを実装し、次にこのパスをクリッピング領域として使用し、その後ピクセルをクリアします。注意すべき点は、最初に描画環境を保存し、ピクセルをクリアした後に描画環境をリセットする必要があることです。リセットしないと、今後の描画はそのクリッピング領域に制限されます。
消去効果ができたので、次はマウスの移動による消去効果を記述します。モバイル版も同様であるため、マウスを使用して、mousedown を touchstart、mousemove に置き換えます。 touchmove で、mouseup で touchend を実行すると、座標点の取得が e.clientX から e.targetTouches[0].pageX に変更されます。
マウスの動きの消去を実装するために、最初はマウスが動いたときにトリガーされるmousemoveイベントでマウスが位置する円形の領域を消去することを考えました。 書き出してみると、マウスが非常に速く動くと、消去された領域が消去されることがわかりました。領域は一貫性を失い、次のような効果が表示されます。これは明らかに私たちが望んでいる消しゴム効果ではありません。
すべての点が一貫性がないため、次に行うことはこれらの点を接続することです。描画機能を実装している場合は、lineTo を介して 2 つの点を直接接続してから描画できますが、消去効果はクリッピング領域です。閉じたパスが必要です。2 つの点を接続するだけでは、クリッピング エリアを形成できません。次に、計算メソッドを使用して、2 つの消去領域内の四角形の 4 つの終点座標を計算することを考えました。これは、下の図の赤い四角形です:
計算方法も非常に簡単です。2 つのクリッピング領域を結ぶ線の 2 つの端点の座標がわかり、必要な線の幅もわかるため、長方形の 4 つの端点の座標は次のようになります。見つけやすいので、以下のコードがあります: XML/HTML コード 内容をクリップボードにコピー
var a asin = a*Math.sin(Math.atan((y2-y1)/(x2- x1)));
var a 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つの端点の座標が得られます。このように、クリッピング領域は円と四角形で構成され、コードは次のように構成されます: XML/HTML コード コンテンツをクリップボードにコピー
var hastouch = "ontouchstart" in window?true:false,//モバイルデバイスの場合
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 ;
// マウスの初回クリック時に円形領域を消去し、同時に最初の座標点を記録します
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);
});
//このイベントはマウスが移動するとトリガーされます
関数 TapmoveHandler(e){
e.preventDefault()
x2 = hastouch ?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft ;
y2 = hastouch ?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop ;
//2 点間のクリッピング領域の 4 つの端点を取得します
var a asin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)) );
var a 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;
// 線の連続性を確保するため、長方形の一端に円を描きます
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();
// 長方形のクリッピング領域内のピクセルをクリアします
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();
//最後の座標を記録します
x1 = x2 ;
y1
= y2 ;
}
})
このようにして、マウスの消去効果が得られますが、もう 1 つ達成すべき点があります。これは、ほとんどの消去の効果で、一定のピクセル数を消去すると、すべての画像コンテンツが自動的に表示されます。効果を実現するには imgData を使用します。コードは次のとおりです: コードをコピー
XML/HTML コード コンテンツをクリップボードにコピー
var
imgData
= ctx .getImageData(0,0,canvas.width,canvas.身長);
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 dd
}
}
if(dd/(imgData.width*imgData.height) <0.4 ){
canvas.className = "noOp" ;
}
imgDataを取得し、imgData内のピクセルを走査し、imgDataのデータ配列内のrgbaのアルファを解析、つまり透明度を解析する ピクセルを消去すると透明度が0になります。現在のキャンバス内の透明度がゼロでないピクセルの数と、透明度がゼロでないピクセルの割合が 40% 未満の場合は、60% を超えることになります。現在のキャンバス上の領域を消去すると、画像が自動的に表示されます。
ここで、mouseup イベントにピクセルをチェックするコードを入れていることに注意してください。これは、ユーザーがマウスを乱暴にクリックすると、mouseup イベントが乱暴にトリガーされ、狂ってしまうことを意味するためです。ピクセルを計算するためにそのループをトリガーすると、計算量が多すぎるためプロセスがブロックされ、インターフェイスがスタックしてしまいます。解決策は次のとおりです。タイムアウトを追加してピクセル計算の実行を遅らせ、毎回タイムアウトをクリアします。ユーザーがクリックした時間、つまりユーザーが長時間クリックした場合、この計算はトリガーされなくなります。上で書いた方法は、ピクセルごとにチェックすることです。ピクセル数が大きすぎると確実にスタックしてしまうので、30 ピクセルごとにチェックするなどのランダム検査を使用できます。 修正されたコードは次のとおりです: コードをコピー
XML/HTML コード コンテンツをクリップボードにコピー
タイムアウト = 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 dd
}
if(dd/(imgData.width*imgData.height/900) <0.4 ){
canvas.className = "noOp";
}
},100)
これにより、ユーザーが乱暴にクリックすることを最大限に防ぐことができます。他にもっと良いチェック方法がある場合は、ご意見をお聞かせください。
このステップではすべてが書かれていましたが、結果はまだ楽観的ではなかったので、最終的に、globalCompositeOperation 属性を見つけました。このプロパティのデフォルト値は、source-over です。つまり、既存のピクセルに描画するときにオーバーレイされますが、destination-out と呼ばれるプロパティもあります。正式な説明は、次のとおりです。ソース画像。ソース イメージの外側のターゲット イメージの部分のみが表示され、ソース イメージは透明になります。理解するのが難しいように思えますが、実際に試してみると非常に簡単であることがわかります。つまり、既存のピクセルに基づいて描画すると、描画した領域内の既存のピクセルが透明になります。画像を見て直接変更できます:
globalCompositeOperation 属性の効果の図。 この属性を使用すると、クリップを使用する必要がなくなり、クリッピング領域を計算するために sin や cos を使用する必要がなくなり、コストが大幅に削減されます。計算が削減され、描画環境 API の呼び出しが削減され、パフォーマンスが向上し、Android での実行がよりスムーズになるはずです。 以下は、変更されたコードです:
XML/HTML コード コンテンツをクリップボードにコピー
//globalCompositeOperation を変更して消去効果を実現
関数 TapClip(){
var hastouch = "ontouchstart" in window?true:false,
tapstart = hastouch ?"touchstart":"mousedown",
tapmove = hastouch ?"touchmove":"mousemove",
tapend = hastouch ?"touchend":"mouseup";
Canvas.addEventListener(tapstart, function(e){
clearTimeout(タイムアウト)
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" // 線の両端を円弧として設定します ; >
ctx.lineJoin = "round" // 円弧に曲がる線を設定します
ctx.lineWidth = a *2;
ctx.globalCompositeOperation = "宛先出力" ;
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(){ 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)
});
function tapmoveHandler(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.ストローク();
ctx.restore()
x1 = x2 ;
y1 = y2 ;
}
})
}
擦除那部分代码就这么一点,也就相当画图機能,直接設定line属性後通過lineTo行绘制線上条,只要事前操作globalCompositeOperation设在destination-out,你所行的一切绘制,都变成マウススライドのイベントフィールドコードも大幅に削減され、オブジェクトの使用回数も削減され、計算も削減され、パフォーマンスが大幅に向上しました。
コードを変更した後、すぐに自分の Android マシンがテストを行った結果、このように、相対的に多くのストリームが送信され、少なくとも顧客の要求に応じた機能が実現されました。
出典地址:
https://github.com/whxaxes/canvas-test/blob/gh-pages/src/Funny-demo/clip/clip.html
声明: この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。