본문 바로가기
Frontend/03. Java Script

[javascript] 모바일 서명 패드 만들기

by 말랑구미구미 2023. 12. 5.

터치 또는 마우스 드래그를 통한 서명을 받는 canvas 영역을 구현해야 했다.

나는 서명 그림을 png 또는 jpg로 저장하는것이 아닌 Base64 코드로 DB에 저장하는 방식을 채택했다.

 

<div class='padsection'>
	<canvas id='drawCanvas' width='300px' height='150px' style='position: relative; border:1px solid black; background-color:#ffffff'></canvas>
</div>

화면은 상단과 같이 간단하게 구성했다.

물론 하단에 서명을 받은 뒤 넘어가는 넘어가는 "저장"과 같은 버튼이 있지만 이 글에선 생략하겠다.

 

(function(obj){
		obj.init();
		$(obj.onLoad);
	})((function(){
		canvas = $("#drawCanvas");
		var div = canvas.parent("div");
		
		//캔버스의 오브젝트를 가져온다
		var ctx = canvas[0].getContext("2d");
		var drawble = false;
		
		function canvasResize(){
			canvas[0].height = div.height();
   			canvas[0].width = div.width();
   			canvas[0].top = window.pageYOffset + div.getBoundingClientRect().top;
		}

 

상단의 코드를 통해 캔버스의 오브젝트를 가져왔다.

그리고 canvasResize() 를 통해 터치되는 곳과 캔버스의 영점을 맞췄다. 

 

처음 작성시엔 다른 블로그에서 참조했던 대로 

function canvasResize(){
			canvas[0].height = div.height();
   			canvas[0].width = div.width();
		}

위와 같이 작성했고 pc에서 동작했을땐 문제가 없었다.

하지만 모바일 chrome에서 동작했을때 터치되는 곳과 그림이 그려지는 화면 사이에 수직으로 차이가 발생했다.

 

그래서 추가한 코드가 아래와 같다

canvas[0].top = window.pageYOffset + div.getBoundingClientRect().top;

 

그 뒤 이벤트 발생을 담당하는 코드는 아래와 같다

//pc에서 사용할 때 사용되는 이벤트
		function draw(e){
			function getPosition(){
				var rect = canvas[0].getBoundingClientRect();
				return {
					X : e.pageX - rect.left,
					Y : e.pageY - rect.top
				}
			}
			switch(e.type){
				case "mousedown" : {
					drawble = true;
					ctx.beginPath();
					ctx.moveTo(getPosition().X, getPosition().Y);
				}
				break;
				case "mousemove" : {
					if(drawble){
						ctx.lineTo(getPosition().X, getPosition().Y);
						ctx.stroke();
					}
				}
				break;
				case "mouseup":
				case "mouseout" :{
					drawble = false;
					ctx.closePath();
				}
				break;
			}
		}
		//스마트 폰에서 서명을 할 경우
		function touchdraw(e){
			function getPosition(){
				var rect = canvas[0].getBoundingClientRect();
				return {
					X: e.changedTouches[0].pageX - rect.left,
					Y: e.changedTouches[0].pageY - rect.top
				}
			}
			switch(e.type){
				case "touchstart" : {
					drawble = true;
					ctx.beginPath();
					ctx.moveTo(getPosition().X, getPosition().Y);
				}
				break;
				case "touchmove" : {
					if(drawble){
						//스크롤 이동중 이벤트 중지
						if(e.cancelable) e.preventDefault();
						ctx.lineTo(getPosition().X, getPosition().Y);
						ctx.stroke();
					}
				}
				break;
				case "touchend":
				case "touchcancel" : {
					drawble = false;
					ctx.closePath();
				}
				break;
			}
		}
		return {
			init: function(){
				//캔버스 사이즈 조절 
				$(window).on("resize", canvasResize);
				
				canvas.on("mousedown", draw);
				canvas.on("mousemove", draw);
				canvas.on("mouseup", draw);
				canvas.on("mouseout", draw);
				//모바일 터치 이벤트
				canvas.on("touchstart", touchdraw);
				canvas.on("touchend", touchdraw);
				canvas.on("touchcancel", touchdraw);
				canvas.on("touchmove", touchdraw);
				
			},
			onLoad: function(){
				canvasResize();
			}
		}
	})());