ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 초보자도 만들 수 있는 자바스크립트로 그림판 만들기(텍스트 추가하기 기능)
    Study/JavaScript 2024. 3. 14. 18:13
    반응형

    저번에 캔버스(canvas)를 이용하여 그림판과 같이 그림 그리기 기능을 만들었음.

    혹시 궁금하시다면 아래 링크 참고하시길....

    https://cbn1218.tistory.com/25

     

    초보자를 위한 캔버스(Canvas)로 그림 그리기 방법

    캔버스(canvas)는 간단하게 소개하자면 웹에 도형을 표시하고싶거나 그림등...그래픽적인 기능이 필요할때 사용 하는 태그로 html5가 나오면서 같이 나온 기술이라고 한다. 프로젝트를 진행중 웹상

    cbn1218.tistory.com

     

    이번 컨텐츠는 위에 캔버스를 사용하여 그림 그리기 기능을 이미 구현 했다는 가정하에

    그림판처럼 텍스트를 넣어서 텍스트의 위치를 자유자재로 옮기거나 수정 또는 삭제를 하고 싶을때 어떻게 하는지 궁금해 하는 분들이 있을거 같은데, 그부분에 대해서 함 구현해 보았움.

     

    일단 결과 화면 먼저 ㄱㄱ

     

    1.결과

     

     

    2.설명

    동영상을 보면 메모 와 그리기 버튼이 있는데, 버튼에 따라 메모모드와 그리기모드를 전환해가며 두가지 기능모두 사용이 가능함. 

    이렇게 모드를 2가지로 나눈이유는 캔버스에서 text입력이 가능한 ctx.fillText("텍스트", x좌표, y좌표, 최대 너비) 를 이용할 수 있으나 입력한 text를 캔버스에서 객체로 인식을 하지 못하기 때문에 텍스트 이동에 문제가 있어 

    모드를 나누어 그리기는 캔버스에서 텍스트 입력은 특정 div안에서 입력후 자유롭게 이동가능하게끔 하기위해 나누었음.

    (1) html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    
    // css들어갈 부분
    <style></style>
    
    </head>
    
     <body>
        <canvas id="myCanvas" width="500px" height="500px"></canvas>
        <div id="memoDiv"></div>
        <button id="memo">메모</button>
        <button id="drow">그리기</button> 
        
        //스크립트 코드 들어갈 부분
        <script></script>
        
    </body>
    
    </html>

     

    css 들어갈 부분과 javaScript 들어갈 위치는 지정해 줬으니 아래 코드 각각 따서 합치면 될듯

    일단은 html구조만 설명하자면 위에서 말했듯이 2가지 모드를 나누었기 때문에 버튼도 2개 생성하고

    그리기는 canvas 에서 활동을 하고 텍스트는 div에서 활동을 하기 때문에 활동영역이 다르므로 각각 태그요소를 지정해줌.

    참고로 id지정해줘야 자바스크립트단에서 컨트롤 할수 있으니 각 태그마다 id 지정해주기

     

    (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; 
        }
        #drow{
           position: absolute;
           top:40px;
           left: 510px; 
        }
    
        .text-input{
            position: absolute;
        }
    
        .text-object{
            position: absolute;
            cursor: move;
        }
    </style>

     

    자유롭게 배치하기 위해 position 을 absolute 로 지정을 했음.

    #mycanvase 와 #memoDiv 를 같은 위치에 겹쳐 두어야 위에 영상처럼 그리기선 과 글자가 같은 canvas위에 있는것 처럼 보임.

    그리고 #mycanvase 와 #memoDiv의 width 와 height 를 같은 크기를 맞춰 줘야하는데,

    #mycanvase의 width 와 height 는 태그에 직접 설정을 해줬음. cavas는 렌더링 컨텍스트의 실제 크기를 임의로 지정이 되어있음. 근데, css에서 설정을 해주면 렌더링 컨텍스트의 실제 픽셀 수를 변경하는 것이 아니라 캔버스의 레이아웃을 확대하는 개념이라고함. 그래서 태그에 직접 설정을 해줘야 렌더링 컨텍스트의 실제 픽셀 수를 변경이 되어 선이 잘그어짐.

    (참고로 마우스와 선을 긋는 위치가 안 맞아 삽질해서 찾은 내용이라 기록함.)

    그리고 z-index 설정한 부분이 있는데, 이것도 #mycanvase 와 #memoDiv 가 겹쳐지면서 메모모드일때 그리기가 안되는이유가 레이아웃이 겹쳐서 마우스이벤트를 인식못하기 때문에 그리기모드일때는 z-index 을 설정하여 제일 위에 레이아웃을 올려 마우스를 인식 할 수 있게끔 해야함.

     

    (3) javaScript

    <script>
        // 컨트롤할 엘리먼트를 취득
        const canvas = document.getElementById('myCanvas');
        const memoDiv = document.getElementById('memoDiv');
        const memo = document.getElementById('memo');
        let painting = false; // 그리기의 여부를 확인하기 위한 변수설정
    
    
    
        drow.addEventListener('click', function(event) { //그리기 버튼을 누름
        // 2d모드의 그리기 객체를 취득한다. => 이 객체를 통해 canvas에 그림을 그릴 수 있다.
            const ctx = canvas.getContext("2d");      
    
            //그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함
            canvas.style.zIndex = 999; 
    
            function stopPainting(){
                painting=false;
            }
    
            function startPainting(){
                painting=true;
            }
    
            function onMouseMove(event){
    
                //마우스 위치 좌표
                var x1 = event.clientX-10 ;
                var y1 = event.clientY-10 ;
    
                if(!painting){ //위에 그리기여부에 따라 painting 이 ture면 선을 그리기 시작하고, false면 선그리기를 멈춘다.
                    ctx.beginPath(); // 새로운 경로 지정을 위한 선언
                    ctx.moveTo(x1,y1); //좌표를 이동
                }else{
                    ctx.lineTo(x1,y1); //현재 지점에서 (x,y)좌표까지 선으로 그림
                    ctx.stroke(); //선의 종류를 지정해줌 (외곽선)
                }
            }
    
            if(canvas){
                canvas.addEventListener("mousemove",onMouseMove);//마우스가 움직일때 onMouseMove 메서드가 작동해라 
                canvas.addEventListener("mousedown",startPainting);//마우스를 눌렀을때 startPainting 메서드가 동작해라
                canvas.addEventListener("mouseup",stopPainting);//마우스를 떼었을때 stopPainting 메서드가 동작해라
                canvas.addEventListener("mouseleave",stopPainting);//마우스가 벗어났을때 stopPainting 메서드가 동작해라
            }
    
        });
    
        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>

     

    자세한 설명은 주석을 달아놨으니 참고 하심 될거 같고,

    코드의 큰흐름만 설명하자면, 

    위에서 설명했듯이 그리기모드 와 메모모드 2가지의 모드가 있으며, 2개가 레이아웃을 겹쳐놓고 버튼에 따라 레이아웃을 전환할 수 있게 함.

     

    그리기 기능은 이전에 만든 컨텐츠가 있어서 따로 설명은 안 할 예정 이니, 궁금하면 아래링크 참조 https://cbn1218.tistory.com/25

     

    (1) 메모 모드를 누르면 캔버스 레이아웃을 제일 하단으로 위치하게 하고 마우스이벤트를 인식할 수 있게끔 memoDiv 레이아웃을 최상단에 위치하게 함. 

    (2) input창을 생성하여 텍스트를 입력받고 마우스가 focus 상태가 아닐때 입력받은 텍스트 내용은 createTextObject() 함수로 넘기고 생성한 input창은 삭제 함.

    (3) createTextObject() 함수로 넘어온 텍스트는 새로 생성한 textObject div 에 전달하여 아래와 같이 div 의 내용으로 인식

    <div class="text-object" style="left: 65px; top: 33px;">ㅇㅇㅇ</div>

     

    input창의 내용을 div를 생성해서 내용에 넣는 이유는 이동 과 수정을 하기 위해서 div의 형태가 되어야 자바스크립트단에서 컨트롤 할 수 있음

     

    (4) 생성된 textObject div 를 이동할때는 'mousedown' 이벤트를 활용하여 마우스가 움직이는 좌표를 이벤트리스너로 받아

    textObject div 의 style="left: 65px; top: 33px;" 를 바꿔가면서 실시간으로 div가 움직이는 걸 볼 수가 있음.

    (5) 수정시 'dblclick' 이벤트(더블클릭이벤트)가 발생시 내용을 다시 받아야 하기 때문에 input창을 다시 생성하는데 기존에 있던 내용을 받아 미리 입력해놓고 새로운 내용도 input 창으로 받아 마우스가 focus 상태가 아닐때 새로받은 내용을 

    기존의 textObject div 의 내용으로 대체 함. 그리고 input 창은 지움.

    (6) 만약 새로 입력하는 내용이 아무것도 없는 빈칸이라면  해당 textObject div를 삭제해 버리므로서 삭제 기능을 구현할 수 있다.

     

    그럼... 끝....

    반응형

    댓글

Designed by Tistory.