Study/JavaScript

초보자를 위한 웹 그림판 만들기(그리기,메모,블럭지정 또는 모양넣기)

nana1002 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>

 

 

 

 

 

 

웹캔버스 누가 쓰냐 하지만 프론트엔드 개발자들한테 좌표개념을 알수있는 기능이므로

알아두면 플젝때 도움될거 같음.

 

그럼...끝....

반응형