Canvas 드래그 그리기
이 장은 Canvas에 이미지와 텍스트를 그리는 방법, 그리고 마우스 드래그 이벤트로 선을 이어 그리는 방법을 다룹니다.
마우스 드래그 그리기는 마지막 좌표와 현재 좌표를 계속 선으로 연결하는 구조입니다. drawing 변수는 마우스가 눌린 상태인지 알려주는 스위치입니다.
| 상태 | 의미 | 변경 시점 |
|---|---|---|
| startX/startY | 마지막 좌표 | mousedown, mousemove 후 |
| drawing | 그리는 중 여부 | down true, up/out false |
| curX/curY | 현재 좌표 | mousemove |
| context | 그리기 도구 | init에서 생성 |
첫 번째 버전은 canvas.addEventListener로 mousemove, mousedown, mouseup, mouseout 리스너를 등록합니다.
마우스 좌표는 e.offsetX, e.offsetY로 읽습니다. 이 값은 이벤트 대상인 canvas의 왼쪽 위를 기준으로 한 좌표라 그리기에 적합합니다.
| 좌표 | 기준 | 용도 |
|---|---|---|
| offsetX | canvas 내부 x | 그리기 x 좌표 |
| offsetY | canvas 내부 y | 그리기 y 좌표 |
| clientX | 브라우저 viewport | 화면 위치 |
| pageX | 문서 전체 | 스크롤 포함 위치 |
draw 함수는 startX/startY에서 curX/curY까지 선을 긋습니다. 그린 뒤 현재 좌표를 다시 시작 좌표로 저장해야 연속 선이 됩니다.
두 번째 버전은 GraphicEditor 클래스로 canvas, context, startX, startY, drawing 상태를 객체 안에 묶습니다.
| 프로퍼티 | 역할 | 초기값 |
|---|---|---|
| canvas | 그리기 대상 DOM | myCanvas |
| context | 2D context | getContext 결과 |
| startX/startY | 마지막 좌표 | 0 |
| drawing | 그리는 중 여부 | false |
원문 주석처럼 이벤트 리스너 안의 this는 GraphicEditor 객체가 아니라 canvas를 가리킬 수 있습니다. 그래서 전역 변수 ge를 통해 ge.down(e)처럼 명시적으로 호출합니다.
첫 번째 버전은 addEventListener를 사용하고 두 번째 버전은 onmousedown 속성에 함수를 대입합니다. 둘 다 가능하지만 addEventListener는 여러 리스너를 붙이기 쉽습니다.
| 방식 | 장점 | 주의 |
|---|---|---|
| addEventListener | 여러 리스너 등록 가능 | 코드가 조금 길어짐 |
| onmousedown 대입 | 간단함 | 기존 핸들러를 덮을 수 있음 |
| 클래스 메서드 | 상태를 객체로 묶음 | this 바인딩 주의 |
| 전역 함수 | 입문 단계에서 이해 쉬움 | 전역 변수 증가 |
| 실수 | 증상 | 해결 |
|---|---|---|
| drawing 검사 누락 | 마우스만 움직여도 그려집니다. | if(!drawing) return을 둡니다. |
| start 좌표 갱신 누락 | 선이 한 점에서 계속 뻗습니다. | move 후 startX/startY를 현재 좌표로 바꿉니다. |
| mouseout 처리 없음 | 캔버스 밖에서도 이어질 수 있습니다. | out에서 drawing false 처리합니다. |
| this 바인딩 착각 | 클래스 메서드 호출 오류가 납니다. | ge 또는 bind/arrow 함수를 사용합니다. |
| offset/client 혼동 | 좌표가 어긋납니다. | canvas 내부 좌표는 offsetX/Y를 씁니다. |
| 체크 | 통과 기준 |
|---|---|
| mousedown | 그리기 시작 좌표를 저장합니다. |
| mousemove | drawing 상태일 때만 선을 긋습니다. |
| mouseup | 그리기 상태를 false로 바꿉니다. |
| mouseout | 캔버스 밖으로 나가면 그리기를 멈춥니다. |
| class | 상태와 메서드를 GraphicEditor에 묶습니다. |
| this | 이벤트 핸들러의 this 주의점을 설명합니다. |
| 테스트 | 확인 내용 |
|---|---|
| 클릭 없이 이동 | 선이 그려지지 않아야 합니다. |
| 드래그 | 연속 선이 따라와야 합니다. |
| 마우스 밖으로 이동 | 그리기가 멈춰야 합니다. |
| 선 색 변경 | strokeStyle을 바꿔 색 차이를 봅니다. |
| 클래스 버전 | ge 메서드 호출 흐름을 확인합니다. |
드래그 그리기에서 선이 끊기거나 엉뚱하게 그려지면 drawing 상태와 좌표 갱신을 먼저 확인합니다.
| 문제 | 가능한 원인 | 확인 |
|---|---|---|
| 클릭 없이 선이 그려짐 | drawing 검사 누락 | if(!drawing) return |
| 선이 한 점에서 뻗음 | start 좌표 갱신 누락 | startX = curX |
| 캔버스 밖에서 이어짐 | mouseout 처리 없음 | out에서 false |
| 좌표가 어긋남 | clientX 사용 | offsetX/Y 사용 |
| 클래스 버전 오류 | this 바인딩 문제 | ge.down(e) 호출 |
GraphicEditor 클래스는 캔버스 그리기에 필요한 상태를 하나로 묶습니다. 더 확장하려면 색상, 굵기, 지우기, 초기화 기능도 프로퍼티와 메서드로 넣을 수 있습니다.
| 기능 | 프로퍼티/메서드 | 역할 |
|---|---|---|
| 선 색 | this.color | strokeStyle에 적용 |
| 선 굵기 | this.lineWidth | context.lineWidth에 적용 |
| 지우기 | clear() | clearRect로 전체 삭제 |
| 상태 변경 | setStyle() | 색과 굵기 변경 |
| 이벤트 연결 | bindEvents() | 리스너 등록 분리 |
| 연습 | 목표 |
|---|---|
| 선 굵기 변경 | input range 값으로 lineWidth를 바꿉니다. |
| 선 색 변경 | input color 값으로 strokeStyle을 바꿉니다. |
| 지우기 버튼 | clearRect(0,0,width,height)를 호출합니다. |
| 터치 이벤트 | touchstart/touchmove를 추가합니다. |
| 저장 기능 | canvas.toDataURL로 이미지 저장을 실험합니다. |
- 이벤트 연결 확인: mousedown, mousemove, mouseup, mouseout이 모두 등록되어 있는지 봅니다.
- 상태값 확인: drawing 값이 눌렀을 때 true, 뗐을 때 false로 바뀌는지 확인합니다.
- 좌표 확인: offsetX와 offsetY가 캔버스 내부 기준 좌표인지 콘솔로 찍어봅니다.
- 그리기 순서 확인: beginPath, moveTo, lineTo, stroke 순서를 지킵니다.
- 마우스 이탈 처리: 캔버스 밖으로 나갔을 때도 drawing을 false로 바꿉니다.
| 이벤트 | 주요 역할 | 실패 증상 |
|---|---|---|
| mousedown | 그리기 시작, 시작 좌표 저장 | 눌러도 선이 시작되지 않습니다. |
| mousemove | 현재 좌표까지 선을 그림 | 마우스를 움직여도 화면이 변하지 않습니다. |
| mouseup | 그리기 종료 | 버튼을 떼도 선이 계속 이어집니다. |
| mouseout | 캔버스 이탈 시 종료 | 밖으로 나갔다 들어오면 선이 튑니다. |
| click | 버튼 기능 실행 | 초기화나 저장 버튼이 동작하지 않습니다. |
- 선 굵기와 색상은 전역 변수보다 객체의 속성으로 관리하면 확장하기 쉽습니다.
- 모바일까지 고려한다면 touchstart, touchmove, touchend를 별도로 연결합니다.
- 캔버스 위치가 바뀌는 레이아웃에서는 getBoundingClientRect 기반 좌표 계산이 안전합니다.
- 지우기 기능은 clearRect만 호출하지 말고 배경색을 다시 칠할지 결정합니다.
- 저장 기능을 붙일 때는 toDataURL 결과가 현재 캔버스 상태를 그대로 담는지 확인합니다.
핵심 한 줄 요약
Tags
'php' 카테고리의 다른 글
| Canvas 도형 함수 완전 정리 (0) | 2026.07.15 |
|---|---|
| Canvas 이미지·텍스트 완전 정리 (0) | 2026.07.15 |
| JavaScript 알까기 게임 - 랜덤 배치와 타이머 완전 정리 (0) | 2026.07.15 |
| JavaScript document.write() — 문서에 직접 출력 완전 정리 (0) | 2026.07.15 |
| JavaScript innerHTML · this — 내용 교체와 이벤트 대상 완전 정리 (0) | 2026.07.15 |
왕진 블로그

댓글