이 효과는 최근 프로젝트에서 사용되었는데, 모바일 기기에서는 사진을 긁어 다른 사진을 표시하는 것과 비슷합니다. 렌더링은 다음과 같습니다.
DEMO 오른쪽을 클릭하세요: DEMO
원래는 인터넷에서 흔히 볼 수 있는 데모를 찾아서 적용해보고 싶었습니다. 고객의 요구 사항 때문에 Android에서는 특별히 부드러울 필요는 없으며 최소한 플레이는 가능해야 하지만 온라인에서 찾은 데모는 너무 느리고 전혀 플레이할 수 없습니다. 그래서 그냥 제가 직접 써보고 싶었고, 이 글은 단지 연구 과정을 기록하기 위한 것입니다.
이 스크래핑 효과에 대해 가장 먼저 떠오르는 것은 HTML5 캔버스를 사용하는 것입니다. 캔버스 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를 통해 두 점을 직접 연결한 다음 그릴 수 있지만 삭제 효과는 클리핑 영역입니다. 닫힌 경로가 필요합니다. 단순히 두 점을 연결하면 클리핑 영역이 형성될 수 없습니다. 그런 다음 계산 방법을 사용하여 두 지우기 영역에 있는 직사각형의 4개 끝점 좌표를 계산하는 방법을 생각했습니다. 이는 아래 그림의 빨간색 직사각형입니다.
계산 방법도 매우 간단합니다. 두 클리핑 영역을 연결하는 선의 두 끝점 좌표를 알 수 있고, 원하는 선의 너비도 알 수 있기 때문에 직사각형의 네 끝점 좌표는 찾기 쉬우므로 아래 코드가 있습니다.
XML/HTML 코드클립보드에 콘텐츠 복사
- var aasin = a*Math.sin(Math.atan((y2-y1)/(x2- x1)))
-
var aacos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)) )
-
var x3 = x1
-
var y3 = y1-acos;
var -
x4 = x1-asin;
var
y4-
= y1 acos;
var x5
= -
x2
var y5
= -
y2-acos;
var x6 =
x2-
-asin;
var y6 = y2
acos;
-
x1, y1 및 x2, y2는 두 개의 끝점이므로 네 개의 끝점의 좌표를 얻습니다. 이런 식으로 클리핑 영역은 원 + 직사각형이며 코드는 다음과 같이 구성됩니다.
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 ;
-
- //두 점 사이의 클리핑 영역의 끝점 4개를 가져옵니다.
-
var aasin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)) );
-
var aacos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)) )
-
var x3 = x1
-
var y3 = y1-acos;
var -
x4 = x1-asin;
var
y4-
= y1 acos;
var x5
= -
x2
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;
}
- })
-
이런 식으로 마우스 지우기 효과를 얻을 수 있지만 달성해야 할 또 다른 점이 있는데, 이는 대부분의 지우기 효과입니다. 특정 수의 픽셀을 지우면 모든 이미지 내용이 자동으로 표시됩니다. 효과를 얻으려면 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이 됩니다. 현재 캔버스의 투명도가 0이 아닌 픽셀 수를 캔버스의 총 픽셀 수와 비교합니다. 투명도가 0이 아닌 픽셀의 비율이 40% 미만이면 60% 이상이 된다는 의미입니다. 현재 캔버스의 영역을 삭제하면 그림이 자동으로 표시될 수 있습니다.
여기서는 계산량이 상대적으로 많기 때문에 mouseup 이벤트에 픽셀을 확인하는 코드를 넣었다는 점에 유의하세요. 사용자가 마우스를 격렬하게 클릭하면 mouseup 이벤트가 격렬하게 트리거됩니다. 해당 루프를 트리거하여 픽셀을 계산하면 계산량이 너무 많아 프로세스가 차단되고 인터페이스가 중단됩니다. 해결 방법은 다음과 같습니다. 픽셀 계산 실행을 지연시키는 시간 초과를 추가하고 매 시간마다 시간 초과를 삭제합니다. 사용자가 클릭할 때, 즉 사용자가 빠르게 오랫동안 클릭하면 이 계산이 더 이상 실행되지 않습니다. 개선할 또 다른 방법은 위에서 작성한 방법을 픽셀 단위로 확인하는 것입니다. 픽셀 수가 너무 많으면 확실히 걸리기 때문에 30픽셀마다 확인하는 등의 무작위 검사를 사용할 수 있습니다. 수정된 코드는 다음과 같습니다.
코드 복사
XML/HTML 코드클립보드에 콘텐츠 복사
- 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 dd
-
-
} -
if(dd/(imgData.width*imgData.height/900)-
<
0.4- ){
canvas.className
= - "noOp"
}
},100) -
이 방법을 사용하면 사용자의 난폭한 클릭을 최대한 방지할 수 있습니다. 다른 더 나은 확인 방법이 있으면 의견을 보내주시기 바랍니다. 감사합니다.
이 단계에서는 모든 것이 작성되었으며 테스트할 시간이었습니다. 결과가 여전히 안드로이드에 붙어 있었기 때문에 마지막으로 globalCompositeOperation 속성을 발견했습니다. 이 속성의 기본값은 소스 오버입니다. 즉, 기존 픽셀에 그릴 때 오버레이되지만 대상 출력이라는 속성도 있습니다. 공식적인 설명은 다음과 같습니다. 소스 이미지. 원본 이미지 외부의 대상 이미지 부분만 표시되며 원본 이미지는 투명합니다. 이해하기 어려울 것 같지만 실제로 직접 테스트해 보면 매우 간단하다는 것을 알 수 있습니다. 즉, 기존 픽셀을 기반으로 그리면 그리는 영역의 기존 픽셀이 투명해집니다. 그림을 보면 바로 변경할 수 있습니다. 이해하기 쉽습니다:
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(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" //선의 양쪽 끝을 호로 설정
- ctx.lineJoin = "round" //선 회전을 호로 설정
- 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(){
- 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)
- });
- 함수 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.Stroke();
- ctx.restore()
-
-
x1 = x2;
-
y1 = y2;
- }
- })
- }
擦除那part代码就这么一点,也就当于画图功能,直接设置line属性后过lineTo进行绘线条,只要事前把globalComp ositeOperation设成목적지 출력了擦除效果.鼠标滑动触发的事件里平代码也少了很多,绘图对象的调用数减少了,计算也减少了,性能提升大大滴。
改好代码后就立即一下,果然如此,跟上一个比流畅了很多,至少达到了客要求的能玩的地步了。
源码地址:https://github.com/whxaxes/canvas-test/blob/gh-pages/src/Funny-demo/clip/clip.html
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
-
2024-10-22 09:46:29
-
2024-10-13 13:53:41
-
2024-10-12 12:15:51
-
2024-10-11 22:47:31
-
2024-10-11 19:36:51
-
2024-10-11 15:50:41
-
2024-10-11 15:07:41
-
2024-10-11 14:21:21
-
2024-10-11 12:59:11
-
2024-10-11 12:17:31