-
초보자를 위한 웹 그림판 만들기(그리기,메모,블럭지정 또는 모양넣기)Study/JavaScript 2025. 3. 20. 18:13반응형
오늘은 저번에 이어 웹그림판 만들기에서 좀더 빌드업된 버젼으로
그리기 + 메모 +모양 넣기 또는 블럭을 지정하는 기능을 만듬
나는 그리기 와 메모만 필요하다는 분은 아래 링크 참고
https://cbn1218.tistory.com/56
초보자도 만들 수 있는 자바스크립트로 그림판 만들기(텍스트 추가하기 기능)
저번에 캔버스(canvas)를 이용하여 그림판과 같이 그림 그리기 기능을 만들었음. 혹시 궁금하시다면 아래 링크 참고하시길.... https://cbn1218.tistory.com/25 초보자를 위한 캔버스(Canvas)로 그림 그리기
cbn1218.tistory.com
1.결과
일단 무엇을 만들수 있는지가 제일 궁금하니 먼저 결과 화면 ㄱㄱ
그림판처럼 그리기, 메모, 모양넣기 또는 블럭지정이가능한 기능을 만들수 있음
2.설명
(1)html
<!DOCTYPE html> <html lang="en"> <head> </head> <body> <canvas id="myCanvas" width="500px" height="500px"></canvas> <div id="memoDiv"></div> <button id="memo">메모</button> <button id="draw">그리기</button> <button id="dragRec">블럭지정</button> </body> </html>
html 코드는 의외로 간단함
눈여겨볼거는 canvas 태그를 넣어야 된다는것과
"memoDiv" 태그를 만들어야함.
그리기와 블록지정은 canvas 를 활용하고 메모기능은 memoDiv 를 활용한다.
(2)css
<style> #myCanvas{ position: absolute; border: 1px solid black; z-index: 999; } #memoDiv{ width: 500px; height: 500px; position: absolute; border: 1px solid red; } #memo{ position: absolute; left: 510px; } #draw{ position: absolute; top:40px; left: 510px; } #dragRec{ position: absolute; top:80px; left: 510px; } .text-input{ position: absolute; } .text-object{ position: absolute; cursor: move; } </style>
주의깊게 봐야 될 부분은 #myCanvas 스타일에 z-index: 999; 를 지정한 부분인데,
캔버스를 제일상단 레이아웃에 올리고 메모 버튼을 눌렀을때 #memoDiv 태그를 제일 상단에 올려
메모와 그리기를 한번에 사용할 수 있음
즉, 버튼을 누르는거에 따라서 #myCanvas 를 제일상단 레이아웃에 올려서 그리거나 블록을 지정하고
#memoDiv를 제일 상단레이아웃에 올려서 메모기능을 쓸 수 있다.
(3)javascript
<script> // 컨트롤할 엘리먼트를 취득 const canvas = document.getElementById('myCanvas'); // 2d모드의 그리기 객체를 취득한다. => 이 객체를 통해 canvas에 그림을 그릴 수 있다. const ctx = canvas.getContext("2d"); const memoDiv = document.getElementById('memoDiv'); const memo = document.getElementById('memo'); const rec = document.getElementById('dragRec'); let painting = false; // 선그리기 시작과 끝을 제어하기 위함 let dragRec = false; // 블럭지정 모드 true or false 선택 let drawLine = false; // 그리기 모드 true or false 선택 let rectangles = []; // 블록을 저장할 배열 let data = []; // 그리기 좌표 저장할 배열 let dataArr = []; //그리는 객체를 각각 저장할때 let stopPainting; let startPainting; let onMouseMove; let startRec; let moveRec; let stopRec; draw.addEventListener('click', function(event) { //그리기 버튼을 누름 dragRec = false; //블럭모드 끄고 drawLine = true; //그리기모드 켜고 canvas.removeEventListener("mousedown",startRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 canvas.removeEventListener("mousemove",moveRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 canvas.removeEventListener("mouseup",stopRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 if(drawLine){ //그리기모드가 ture 일때 아래 이벤트를 걸수 있음 //그리기 색지정 ctx.strokeStyle = 'black'; //그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함 canvas.style.zIndex = 999; stopPainting = function (){ painting=false; if(data && data.length > 0){ dataArr[dataArr.length] = data //객체별로 그린 좌표묶음을 배열에 저장 data = [ ] //초기화 } } startPainting = function (){ painting=true; } onMouseMove =function (e){ //마우스 위치 좌표 var x1 = e.clientX-10 ; var y1 = e.clientY-10 ; if(!painting){ // ture 선을 그리기 시작, false 선그리기를 멈춤 ctx.beginPath(); // 새로운 경로 지정을 위한 선언 ctx.moveTo(x1,y1); //선그리기전 좌표를 이동 }else{ ctx.lineTo(x1,y1); //현재 지점에서 (x,y)좌표까지 선으로 그림 ctx.stroke(); //선의 종류를 지정해줌 (외곽선) data.push({ x: x1, y: y1}); } } canvas.addEventListener("mousemove",onMouseMove);//마우스가 움직일때 onMouseMove 이벤트걸기 canvas.addEventListener("mousedown",startPainting);//마우스를 눌렀을때 startPainting 이벤트걸기 canvas.addEventListener("mouseup",stopPainting);//마우스를 떼었을때 stopPainting 이벤트걸기 canvas.addEventListener("mouseleave",stopPainting);//마우스가 벗어났을때 stopPainting 이벤트걸기 } }); rec.addEventListener('click', function(event) { //블럭지정 버튼을 누름 dragRec = true; //블럭모드 켜고 drawLine = false; //그리기 모드 끄기 canvas.removeEventListener("mousedown",startPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mousemove",onMouseMove); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mouseup",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mouseleave",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 if(dragRec){ //그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함 canvas.style.zIndex = 999; ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; var draw = false; //블럭의 시작과 끝의 제어하기 위한 변수 startRec= function (e){ var startX = parseInt(e.clientX - canvas.getBoundingClientRect().left); var startY = parseInt(e.clientY - canvas.getBoundingClientRect().top); var rectangle = { startX: startX, startY: startY, endX: startX, endY: startY }; rectangles.push(rectangle); drawRectangles(); // 모든 네모들을 다시 그림 draw = true; } moveRec= function (e){ if(draw){ var endX = parseInt(e.clientX - canvas.getBoundingClientRect().left); var endY = parseInt(e.clientY - canvas.getBoundingClientRect().top); rectangles[rectangles.length - 1].endX = endX; //블럭을 지정할때 endX 좌표값을 새로 업데이트 rectangles[rectangles.length - 1].endY = endY; //블럭을 지정할때 endY 좌표값을 새로 업데이트 drawRectangles(); // 모든 네모들을 다시 그림 //앞서 그리기한게 있다면 배열에 있는 좌표값을 꺼내서 다시 그리는 로직 dataArr.forEach(innerArray => { ctx.beginPath(); //새로운 경로 시작 ctx.moveTo(innerArray[0].x, innerArray[0].y); //선그리기전 좌표시작점 innerArray.forEach(item => { ctx.lineTo(item.x, item.y); //점과 점을 잇는부분 ctx.stroke();//실제 선 그리는 부분 }); }); } } stopRec= function (e){ draw = false; } canvas.addEventListener("mousedown",startRec); //마우스를 누를때 startRec 이벤트걸기 canvas.addEventListener("mousemove",moveRec); //마우스 움직일때 moveRec 이벤트 걸기 canvas.addEventListener("mouseup",stopRec); //마우스 뗄때 stopRec 이벤트 걸기 // 모든 네모들을 그리는 함수 function drawRectangles() { ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas rectangles.forEach(function(rectangle) { ctx.fillRect(rectangle.startX, rectangle.startY, rectangle.endX - rectangle.startX, rectangle.endY - rectangle.startY); }); } } }) memo.addEventListener('click', function(event) { //메모 버튼을 누름 //메머버튼을 누름과 동시에 메모기능을 사용할수 있는 div박스가 맨위로 올라와 메모기능사용가능 canvas.style.zIndex = 0; var x = 100; var y = 100; //input 박스를 생성하여 글자를 입력받음 var textInput = document.createElement('input'); textInput.type = 'text'; textInput.classList.add('text-input'); textInput.style.left = x + 'px'; textInput.style.top = y + 'px'; //위에 생성한 input 박스를 해당 div에 적용 memoDiv.appendChild(textInput); textInput.focus(); //마우스가 focus상태가 아닐때 input 박스에서 받은 값을 createTextObject() 함수에 넘겨줌 textInput.addEventListener('blur', function() { if (textInput.value.trim() !== '') { createTextObject(textInput.value, x, y); } memoDiv.removeChild(textInput); // input 박스는 본인역할을 다끝내서 지움 }); }); //텍스트 div에 대한 이벤트 처리 function createTextObject(text, x, y) { //위에 input박스를 통해 입력받은 내용을 div를 생성 var textObject = document.createElement('div'); textObject.textContent = text; textObject.classList.add('text-object'); textObject.style.left = x + 'px'; textObject.style.top = y + 'px'; //생성된 div의 위치에 이벤트가 발생했을때 textObject.addEventListener('mousedown', function(event) { var offsetX = event.clientX - textObject.offsetLeft; var offsetY = event.clientY - textObject.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); //마우스가 움직일때 div의 위치를 이동시켜주는 이벤트 function onMouseMove(event) { var newX = (event.clientX - offsetX - memoDiv.offsetLeft)+10; var newY = (event.clientY - offsetY - memoDiv.offsetTop)+10; textObject.style.left = newX + 'px'; textObject.style.top = newY + 'px'; } //마우스이벤트가 끝났을때 이벤트 리스너 정리 해줌 function onMouseUp() { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } }); // 생성된 div의 내용을 수정할때 textObject.addEventListener('dblclick', function() { //내용을 수정할때 input을 다시 생성하여 기존내용 및 수정하는 내용을 입력 받음 var originalText = textObject.textContent; var inputField = document.createElement('input'); inputField.type = 'text'; inputField.value = originalText; inputField.classList.add('text-input'); inputField.style.left = textObject.style.left; inputField.style.top = textObject.style.top; //마우스가 focus상태가 아닐때 input박스에 입력받은 내용을 기존div에 넘겨주고 혹여 내용이 없으면 div를 삭제 inputField.addEventListener('blur', function() { textObject.textContent = inputField.value; memoDiv.removeChild(inputField); //내용이 없으면 div를 삭제하는 부분 if (textObject.textContent.trim() === '') { memoDiv.removeChild(textObject); } }); //위에 설정한 input 박스를 설정해줌 memoDiv.appendChild(inputField); inputField.focus(); }); //생성한 div를 최종적으로 적용할때 memoDiv.appendChild(textObject); } </script>
자바스크립트가 제일 중요함!
상세한설명은 코드마다 설명을 주석처리함.
한줄한줄 보면서 코드를 따라 쳐보면 도움이 될듯함.
(4)전체코드
설명은 필요없고 전체 코드만 필요하다 하시는분은 아래 코드 참고
<!DOCTYPE html> <html lang="en"> <head> </head> <style> #myCanvas{ position: absolute; border: 1px solid black; z-index: 999; } #memoDiv{ width: 500px; height: 500px; position: absolute; border: 1px solid red; } #memo{ position: absolute; left: 510px; } #draw{ position: absolute; top:40px; left: 510px; } #dragRec{ position: absolute; top:80px; left: 510px; } .text-input{ position: absolute; } .text-object{ position: absolute; cursor: move; } </style> <body> <canvas id="myCanvas" width="500px" height="500px"></canvas> <div id="memoDiv"></div> <button id="memo">메모</button> <button id="draw">그리기</button> <button id="dragRec">블럭지정</button> <script> // 컨트롤할 엘리먼트를 취득 const canvas = document.getElementById('myCanvas'); // 2d모드의 그리기 객체를 취득한다. => 이 객체를 통해 canvas에 그림을 그릴 수 있다. const ctx = canvas.getContext("2d"); const memoDiv = document.getElementById('memoDiv'); const memo = document.getElementById('memo'); const rec = document.getElementById('dragRec'); let painting = false; // 선그리기 시작과 끝을 제어하기 위함 let dragRec = false; // 블럭지정 모드 true or false 선택 let drawLine = false; // 그리기 모드 true or false 선택 let rectangles = []; // 블록을 저장할 배열 let data = []; // 그리기 좌표 저장할 배열 let dataArr = []; //그리는 객체를 각각 저장할때 let stopPainting; let startPainting; let onMouseMove; let startRec; let moveRec; let stopRec; draw.addEventListener('click', function(event) { //그리기 버튼을 누름 dragRec = false; //블럭모드 끄고 drawLine = true; //그리기모드 켜고 canvas.removeEventListener("mousedown",startRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 canvas.removeEventListener("mousemove",moveRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 canvas.removeEventListener("mouseup",stopRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제 if(drawLine){ //그리기모드가 ture 일때 아래 이벤트를 걸수 있음 //그리기 색지정 ctx.strokeStyle = 'black'; //그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함 canvas.style.zIndex = 999; stopPainting = function (){ painting=false; if(data && data.length > 0){ dataArr[dataArr.length] = data //객체별로 그린 좌표묶음을 배열에 저장 data = [ ] //초기화 } } startPainting = function (){ painting=true; } onMouseMove =function (e){ //마우스 위치 좌표 var x1 = e.clientX-10 ; var y1 = e.clientY-10 ; if(!painting){ // ture 선을 그리기 시작, false 선그리기를 멈춤 ctx.beginPath(); // 새로운 경로 지정을 위한 선언 ctx.moveTo(x1,y1); //선그리기전 좌표를 이동 }else{ ctx.lineTo(x1,y1); //현재 지점에서 (x,y)좌표까지 선으로 그림 ctx.stroke(); //선의 종류를 지정해줌 (외곽선) data.push({ x: x1, y: y1}); } } canvas.addEventListener("mousemove",onMouseMove);//마우스가 움직일때 onMouseMove 이벤트걸기 canvas.addEventListener("mousedown",startPainting);//마우스를 눌렀을때 startPainting 이벤트걸기 canvas.addEventListener("mouseup",stopPainting);//마우스를 떼었을때 stopPainting 이벤트걸기 canvas.addEventListener("mouseleave",stopPainting);//마우스가 벗어났을때 stopPainting 이벤트걸기 } }); rec.addEventListener('click', function(event) { //블럭지정 버튼을 누름 dragRec = true; //블럭모드 켜고 drawLine = false; //그리기 모드 끄기 canvas.removeEventListener("mousedown",startPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mousemove",onMouseMove); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mouseup",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 canvas.removeEventListener("mouseleave",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기 if(dragRec){ //그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함 canvas.style.zIndex = 999; ctx.fillStyle = "rgba(255, 255, 0, 0.3)"; var draw = false; //블럭의 시작과 끝의 제어하기 위한 변수 startRec= function (e){ var startX = parseInt(e.clientX - canvas.getBoundingClientRect().left); var startY = parseInt(e.clientY - canvas.getBoundingClientRect().top); var rectangle = { startX: startX, startY: startY, endX: startX, endY: startY }; rectangles.push(rectangle); drawRectangles(); // 모든 네모들을 다시 그림 draw = true; } moveRec= function (e){ if(draw){ var endX = parseInt(e.clientX - canvas.getBoundingClientRect().left); var endY = parseInt(e.clientY - canvas.getBoundingClientRect().top); rectangles[rectangles.length - 1].endX = endX; //블럭을 지정할때 endX 좌표값을 새로 업데이트 rectangles[rectangles.length - 1].endY = endY; //블럭을 지정할때 endY 좌표값을 새로 업데이트 drawRectangles(); // 모든 네모들을 다시 그림 //앞서 그리기한게 있다면 배열에 있는 좌표값을 꺼내서 다시 그리는 로직 dataArr.forEach(innerArray => { ctx.beginPath(); //새로운 경로 시작 ctx.moveTo(innerArray[0].x, innerArray[0].y); //선그리기전 좌표시작점 innerArray.forEach(item => { ctx.lineTo(item.x, item.y); //점과 점을 잇는부분 ctx.stroke();//실제 선 그리는 부분 }); }); } } stopRec= function (e){ draw = false; } canvas.addEventListener("mousedown",startRec); //마우스를 누를때 startRec 이벤트걸기 canvas.addEventListener("mousemove",moveRec); //마우스 움직일때 moveRec 이벤트 걸기 canvas.addEventListener("mouseup",stopRec); //마우스 뗄때 stopRec 이벤트 걸기 // 모든 네모들을 그리는 함수 function drawRectangles() { ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas rectangles.forEach(function(rectangle) { ctx.fillRect(rectangle.startX, rectangle.startY, rectangle.endX - rectangle.startX, rectangle.endY - rectangle.startY); }); } } }) memo.addEventListener('click', function(event) { //메모 버튼을 누름 //메머버튼을 누름과 동시에 메모기능을 사용할수 있는 div박스가 맨위로 올라와 메모기능사용가능 canvas.style.zIndex = 0; var x = 100; var y = 100; //input 박스를 생성하여 글자를 입력받음 var textInput = document.createElement('input'); textInput.type = 'text'; textInput.classList.add('text-input'); textInput.style.left = x + 'px'; textInput.style.top = y + 'px'; //위에 생성한 input 박스를 해당 div에 적용 memoDiv.appendChild(textInput); textInput.focus(); //마우스가 focus상태가 아닐때 input 박스에서 받은 값을 createTextObject() 함수에 넘겨줌 textInput.addEventListener('blur', function() { if (textInput.value.trim() !== '') { createTextObject(textInput.value, x, y); } memoDiv.removeChild(textInput); // input 박스는 본인역할을 다끝내서 지움 }); }); //텍스트 div에 대한 이벤트 처리 function createTextObject(text, x, y) { //위에 input박스를 통해 입력받은 내용을 div를 생성 var textObject = document.createElement('div'); textObject.textContent = text; textObject.classList.add('text-object'); textObject.style.left = x + 'px'; textObject.style.top = y + 'px'; //생성된 div의 위치에 이벤트가 발생했을때 textObject.addEventListener('mousedown', function(event) { var offsetX = event.clientX - textObject.offsetLeft; var offsetY = event.clientY - textObject.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); //마우스가 움직일때 div의 위치를 이동시켜주는 이벤트 function onMouseMove(event) { var newX = (event.clientX - offsetX - memoDiv.offsetLeft)+10; var newY = (event.clientY - offsetY - memoDiv.offsetTop)+10; textObject.style.left = newX + 'px'; textObject.style.top = newY + 'px'; } //마우스이벤트가 끝났을때 이벤트 리스너 정리 해줌 function onMouseUp() { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } }); // 생성된 div의 내용을 수정할때 textObject.addEventListener('dblclick', function() { //내용을 수정할때 input을 다시 생성하여 기존내용 및 수정하는 내용을 입력 받음 var originalText = textObject.textContent; var inputField = document.createElement('input'); inputField.type = 'text'; inputField.value = originalText; inputField.classList.add('text-input'); inputField.style.left = textObject.style.left; inputField.style.top = textObject.style.top; //마우스가 focus상태가 아닐때 input박스에 입력받은 내용을 기존div에 넘겨주고 혹여 내용이 없으면 div를 삭제 inputField.addEventListener('blur', function() { textObject.textContent = inputField.value; memoDiv.removeChild(inputField); //내용이 없으면 div를 삭제하는 부분 if (textObject.textContent.trim() === '') { memoDiv.removeChild(textObject); } }); //위에 설정한 input 박스를 설정해줌 memoDiv.appendChild(inputField); inputField.focus(); }); //생성한 div를 최종적으로 적용할때 memoDiv.appendChild(textObject); } </script> </body> </html>
웹캔버스 누가 쓰냐 하지만 프론트엔드 개발자들한테 좌표개념을 알수있는 기능이므로
알아두면 플젝때 도움될거 같음.
그럼...끝....
반응형'Study > JavaScript' 카테고리의 다른 글
초보자도 만들 수 있는 달력으로 일정 및 시간 예약 확정 알림 만들기 (2) 2025.03.26 초보자도 만들 수 있는 파일첨부 및 이미지 미리보기 기능 (0) 2025.01.09 초보자를 위한 가짜 목서버 or 임시api 사용법(포스트맨 최신 버젼)_2 (0) 2024.04.29 초보자를 위한 가짜 목서버 or 임시api 사용법(포스트맨 최신 버젼)_1 (0) 2024.04.25 초보자도 만들 수 있는 자바스크립트로 그림판 만들기(텍스트 추가하기 기능) (1) 2024.03.14