ESTsoft/사전 VOD

[JS] 자바스크립트 API 활용 및 그림판 프로그램 만들기

효땡 2024. 5. 27. 20:46

[API 활용]

클라이언트 측 고유 기술 요소

- 웹브라우저에서 동작하는 자바스크립트를 클라이언트 측 자바스크립트라 함

- 클라이언트 측 자바스크립트 구성

  -> ECMAScript가 규정한 코어 언어와 웹 브라우저의 API(Application Program Interface)

- 웹 브라우저의 주요 API

  * Window 인터페이스

    -> 자바스크립트로 부라우저 또는 창을 조작하는 기능 제공

  * DOM

   -> 자바스크립트로 HTML 문서의 요소를 제어하는 기능 제공

  * XMLHttpRequest

   -> 서버와 비동기로 통신하는 기능 제공

 

서버 측 자바스크립트의 고유 기술 요소

- 웹 서버에서 동작하는 자바스크립트

- 웹 서버를 구현하는데 Perl, PHP, Python, Ruby 등의 프로그래밍 언어를 사용

- 웹 브라우저의 주요 API

  * Node.js

    -> 구글이 개발한 자바스크립트 실행 환경

  * Rhino

    -> 오픈 소스로 개발되어 현재는 모질라가 관리하고 있는 자바스크립트 실행 환경

  * Aptana Jaxer

    -> 압타나 사가 개발하고 현재는 오픈소스로 개발되고 있는 자바스크립트 실행 환경

 

주요 API

API 설명
Drag and Drop HTML 요소 혹은 파일을 끌어서(드래그) 다른 HTML 요소에 놓을 때(드롭할 때) 데이터를 전달하는 기능을 제공
Blob 이진 데이터를 다루는 기능을 제공
File 프로그램 여러 개를 멀티스레드를 병렬 처리한느 기능을 제공
Web Workers 대용량이며 저장 기간에 제한이 없는 데이터를 로컬에 저장하는 기능을 제공
Indexed Database 로컬에 키값 타입의 관계형 데이터 베이스 기능을 제공
WebSockets 서버와의 양방향 통신 기능을 제공
Geolocation GPS 등의 위치 정보를 다루는 기능을 제공
Canvas 2차원 3차원 그래픽스 기능을 제공

 

드래그 앤 드롭 API

- HTML 요소나 로컬 파일을 마우스로 끌어서 옮길 수 있으며 다른 요소에 드롭할 수 있음

- 이때 드래그한 요소 또는 파일의 데이터는 드롭 타킷 요소에 전달

- HTML 요소를 드래그 할 수 있게 만들기

  * <idv draggable="true"> 드래그 할 수 있습니다. </div>

  * 이 속성을 지정하지 않거나 auto로 지정하면 해당 HTML요소의 기본값을 사용

  * href 속성을 지정한 a요소와 src속성을 지정한 img요소는 기본적으로 드래그 할 수 있도록 만들어져 있음

 

드래그 앤 드롭 이벤트

API 설명
dragstart 드래그를 시작할 때 발생
drag 드래그를 하는 동안 발생
dragend 드래그가 끝났을 때 발생
dragenter 마우스 포인터가 드롭 요소의 경계선 안쪽으로 들어갈 때 발생
dragover 마우스 포인터가 드롭 요소의 경계선 안쪽에 있을 때 발생
dragleave 마우스 포인터가 드롭 요소의 경계선 바깥으로 나왔을 때 발생
drop 요소에 드롭할 때 발생

 

드래그 앤 드롭 API

- 모든 드래그 앤 드롭 이벤트는 dataTransfer 프로퍼티를 가짐

- dataTransfer 프로퍼티 값은 Datatransfer 객체이며, 이 객체로 드래그 타깃 요소가 드롭 타깃 요소에 데이터를 전달할 수 있음

- DataTransfer 객체의 프로퍼티와 메소드

 

드래그 앤 드롭 이벤트

프로퍼티 이름 / 메소드 이름 설명
type setData 메소드로 설정한 데이터 타입 목록
files 드래그한 파일 객체 목록
effectAllowed 드래그 타킷 요소가 허용하는 작업의 유형 {"none", "copy", "copyLink", "copyMove" 등}
dropEffect 드롭 타깃 요소에 표시하는 효과 {"none", "copy", "move", "link"}
setData(format, data) 드래그 타깃 요소의 데이터 타입을 특정 데이터 타입으로 설정
getData(format) 드롭 타깃 요소에서 데이터를 특정 타입(format)으로 가져옴
clearData(format) Format타입으로 저장된 데이터를 삭제, format으로 지정하지 않으면 모든 데이터 삭제
setDragImage(element x, y) 드래그 이미지(드래그 중 포시되는 이미지)를 설정, Element는 img요소, x는 이미지 수평 오프셋, y는 이미지 수직 오프셋
addElement(element) 드래그 타깃의 HTML요소를 드롭 타깃에 추가. 이 메소드를 호출하지 않아도 드롭하는 요소 자체가 드래그 타깃의 HTML요소가 됨

 

데이터 전달하기(드래그 타깃 요소에서 드롭 타깃 요소에 데이터 전달)

- 드래그 타깃 요소의 dragstart이벤트 처리기 안에서 data Transfer 프로퍼티의 setData메소드에 데이터 타입을 지정한 데이터를 추가 

e.dataTransfer.setData("text/plain", value);

- 드롭 타깃 요소의 dragover 이벤트 처리기 안에서 브라우저의 기본 동작을 취소 드래그 타깃 요소가 드롭 타깃 요소 위에 올라가면 브라우저의 기본 동작인 drop 이벤트가 취소되기 때문에 e.preventDefault();

- 드롭 타깃 요소의 drop 이벤트 처리기 안에서 dataTransfer 프로퍼티의 getData 메소드를 사용해서 데이터를 지정한 데이터 타입으로 가져옴

- var value = e.dataTransfer.getData("text/plain");

  데이터는 데이터 타입(format)별로 하나만 전달할 수 있음

- setData메소드로 같은 데이터 타입의 데이터를 두 번 설정하면 이전에 설정한 데이터를 덮어씀

 

드래그 앤 드롭 예제

- 드롭하는 영역을 사용자에게 알림

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
    <title>드롭하는 영역을 사용자에게 알리기</title>
    <script>
    	window.onload = function() {
        	var dragbox = document.getElementById("dragbox");
            var dropbox = document.getElementById("dropbox");
            
            dropbox.addEventListener("dragenter", function(e) {
            	e.target.style.borderColor = "red";
            }, false);
            dropbox.addEventListener("dragleave", function(e) {
            	e.target.style.borderColor = "gray"
            }, false);
            dropbox, addEventListener("drop", function(e) {
            	e.target.style.borderColor = gray";
            }, false);
        };
    </script>
    <style>
    	#dragbox {width: 150px; border: 10px solid blue;}
        #dropbox {width: 150px; padding: 50px; border: 10px solid blue;}
    </style>
<body>
	<div id="dragbox" draggable="true">이것을 드래그하세요</div>
    <div id="dropbox">이곳에 드롭하세요</div>
</body>
</head>

 

- 드래그 앤 드롭으로 배경색 설정하기

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>드래그 앤 드롭 예제</title>
<script>
	window.onload = function() {
    	var color = document.getElementById("color");
        var dropbox = document.getElementById("dropbox");
        //드래그를 시작할 때, 색상의 값을 dataTransfer 객체의 데이터로 설정한다.
        color.ondragstart = function(e) {
        	e.dataTransder.setData("text/plain", e.tartget.value);
        };
        //드래그 타깃 요소 위에 마우스 포인터가 올라가면, 브라우저의 기본 동작을 취소한다. (필수)
        dropbox.ondragover = function(e) {
        	e.preventDefault();
        };
        //요소를 드롭하면, datTransfer의 데이터로 보더 박스의 배경색을 설정한다.
        dropbox.ondrop = function(e) {
        	e.preventDefault(); //브라우저의 기본 동작을 취소한다. (선택사항)
            e.target.style.backgroundColor = e.dataTransfer.getData("text/plain");
        };
    };
</script>
<style>
	#color {margin-bottom: 10px;}
    #dropbox {width: 150px; padding: 50px; border: 1px solid gray;}
</style>
</head>
<body>
	<input type="color" id="color" draggable="true">
    <div id="dropbox">이곳에 드롭하세요</div>
</body>
</html>

 

[그림판 프로그램]

그림판 프로그램 실행 화면

- painter.html

- elt.js

- painter.js

 

그림판 프로그램 분석

- painter.html

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
    <title>Simple Painter</title>
    <script src="./elt.js"></script>
    <script src="./painter.js"></script>
    <script>
    	window.onload = function() {
        	createPainter(document.body, 800, 600);
        };
    </script>
</head>
<body>
</body>
</html>

 

- elt.js

/*
* 함수 이름: elt
* 주어진 이름(name)과 속성(attribute), 자식 노드를 포함하는 엘리먼트를 만들어서 반환하는 함수
*/
function elt(name, attribute) {
	var node = document.createElement(name);
    if(attribute){
    	for(var attr in attributes) {
        	if(attributes.hasOwnProperty(attr)) {
            	mode.setAttribute(attr, attributes[attr]);
            }
        }
    }
    for(var i=2; i<arguments.length; i++){
    	var child = argumentss[i];
        if(typeof child == "string") {
        	child = document.createTextNode(child);
        } 
        node.appendChild(child);
    }
    return node;
}

 

- painter.js

/*
화면을 구성하는 요소를 생산하고, 요소에 이벤트 리스너를 등록한다.
*/
function createPainter(parent, width, height) {
	//타이틀
    var title = elt("h2", null, "Simple Painter");
    //canvas 요소와 랜더링 컨텍스트를 가져온다
    var [canvas, ctx] = createCanvas(width, height);
    //도구 막대 : controls 객체의 프로퍼티를 순회하면서 등록한다
    var toolbar = elt("div", null);
    for(var name in controls) {
    	toolbar.appendChild(controls[name](ctx));
    }
    toolbar.style.fontSize = "small";
    toolbar.style.marginBottom = "3px";
    //toolbar 요소와 canvas 요소를 지정한 요소(parent)의 자식 요소로 삽입한다
    parent.appendChild(elt("div", null, title, toolbar, canvas));
}
function createCanvas(canvasWidth, canvasHeight) {
	var canbas = elt("canvas", {width: canvasWidth, height: canvasHeight});
    var ctx = canvas.getContext(2d");
    canvas.style.border = "1px solid gray";
    canvas.style.cursor = "painter";
    //그리기 도구를 mousedown 이벤트의 이벤트 리스너로 등록한다
    canvas.addEventListener("mousedown", function(e) {
    	//Firefox 대책 : 색상을 선택하며, change 이벤트를 강제로 발생시킨다.
        var event = document.createEvent("HTMLEvents");
        colorInput.dispatchEvent(event);
        //선택한 그리기 도구를 초기화
        paintTools[paintTool](e,ctx);
    }, false);
    return [canvas, ctx];
}

 

/*
* 유틸리티
*/

// * element의 왼쪽 위 모서리에서 마우의 상대 위치를 가져온다
function relativePosition(event, element){
	var rect = element.getBoundingClientRect();
    return {
    		x: Math.floor(event.clientX - rect.left),
            y: Math.floor(event.clientY - rect.top)};
}

 

- painter.js

/*
그리기 도구
* paintTools 메소드는 사용할 수 있느 ㄴ기리기 도구의 모음이다.brush
* 각 그리기 도구는, 그림을 그리기 위한 각종 설정과 이벤트 리스너의 등록을 담당한다.
* 각 메소드는 controls.painter를 통해 자동으로 도구 선택 메뉴에 추가된다.
* 메뉴에서 선택한 도구는 변수 apintTool에 저장되며, 그림을 그릴 때 사용한다.
* 그리기 도구를 추가하려면, paintTools 메소드에 새로운 그리기 도구를 추가하여야한다.
*/

var paintTool; //선택된 그리기 도구 (controls.painter로 선택)
var paintTools = Object.create(null); //그리기 도구 객체
// * brush: 브러쉬 도구
paintTools.brush = function(e, ctx) {
	ctx.lineCap = "round";
    ctx.lineJoin = "round";
    //Canvas 화면을 img에 저장한다.
    var img = ctx.getImageDate(0, 0, ctx.canvas.width, ctx.canvas.height);
    //canbas 요소에 대한 마우스 포인터의 상대 위치를 구한다.
    var p = relativePostion(e, ctx.canvas);
    //경로를 정의한다.
    ctx.beginPath();
    ctx.moveTo(p.x,p.y);
    //드래그 이벤트 리스너를 등록한다.
    setDragListeners(ctx, img, function(q) {
    	ctx.lineTo(q.x, q.y); //경로를 추가한다.
        ctx.stroke(); //경로를 그린다.
    });
};

 

그림판 프로그램 분석

- painter.js

// * line: 선 그리기 도구
paintTools.line = function(e, ctx){
	// 1. 그림을 그리기 위한 초기화 처리 : 선의 끝부분을 둥글게 만든다.
    ctx.lineCap = "round";
    // 2. 그림을 그리기 전에, Canvas에 담긴 그림을 img에 저장한다.
    var img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    // 3. canvas 요소에 대한, 마우스 포인터의 상대 위치를 구한다.
    var p = relativePosition(e, ctx.canvas);
    // 4. 마우스를 드래그할 때의 이벤트 리스너를 등록한다.
    setDragListeners(ctx, img, function(q) {
    	ctx.beginPath();
        ctx.moveTo(p.x,p.y); ctx.lineTo(q.x,q.y);
        ctx.stroke();
    });
};
// * circle : 원 그리기 도구
paintTools.circle = function(e, ctx) {
	var img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    var p - relativePosition(e, ctx.canvas);
    setDragListeners(ctx, img, function(q) {
    	var dx - q.x - p.x;
        var dy = q.y - p.y;
        var r = Math.sqrt(dx*dx*dy*dy);
        ctx.beginPath();
        ctx.arc(p.x, p.y, r, 0, 2*Math.PI, false);
        ctx.stroke();
    });
};
// * circleFill : 채워진 원 그리기 도구
paintTools.circleFill = function(e, ctx) {
	var img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    var p = relativePosition(e, ctx.canvas);
    setDragListeners(ctx, img, function(q) {
    	var dx = q.x - p.x;
        var dy = q.y - p.y;
        var r = Math.sqrt(dx*dx+dy*dy);
        ctx.beginPath();
        ctx.arc(p.x, p.y, r, 0, 2*Math.PI, false);
        ctx.fill();
    });
};

 

- painter.js

/*
* 그리기 도구의 유틸리티
*/

// * 마우스를 드래그할 때의 이벤트 리스너를 등록한다.
function setDragListeners(ctx, img, draw) {
	//mousemove 이벤트 리스너를 등록한다.
    var mousemoveEvenListener = function(e) {
    	//저장한 이미지를 읽어들인다.
        ctx.putImageData(img, 0, 0);
        //지정한 그리기 함수 draw로 마우스 위치까지 그린다.
        draw(relativePosition(e, ctx.canvas));
    };
    document.addEventListener("mousemove", mousemoveEventListener, false);
    //mouseup  이벤트 리스너를 등록한다.
    document.addEventListener("mouseup", function(e) {
    	//저장된 이미지로 되돌린다.
        ctx.putImageData(img, 0, 0);
        //지정된 그리기 함수 draw로 마우스 위치까지 그린다.
        draw(relativePosition(e, ctx.canvas));
        //mousemove, mouseup 이벤트 리스너를 제거한다.
        document.removeEventListener("mousemove", mousemoveEventListener, false);
        document.removeEventListener("mouseup", arguments.callee, false);
    }, false);
};
/*
* 컨트롤러
* 각종 설정을 변경하는 제어 도구 목록을 정의한다.
* 각 컨트롤러는 controls 객체의 메소드로 등록되어 있다.
* 각 메소드는 필요한 HTML 요소를 생성해서 변환하며, 이벤트 리스너를 등록한다.
* 각 메소드는 createPainter를 통해 자동으로 도구 막대에 추가한다.
* 새로운 컨트롤을 추가하려면, controls 객체에 새로운 메소드를 추가해야한다.
*/

var controls = Object.create(null); //컨트롤러 객체
var colorInput; //Firefox의 change 이벤트 대책. input[type="color"] 객체를 저장한다.
// * 그리기 도구 선택
controls.painter = function(ctx) {
	var DEFAULT_TOOL = 0;
    var select = elt("select", null);
    var label = elt("label", null, "그리기 도구 : ", select);
    for(var name in paintTools) {
    	select.appenedChild(elt("option", {value: name}, name));
    }
    select.selectedIndex = DEFAULT_TOOL;
    paintTool = select.children[DEFAULT_TOOL].value;
    select.addEventListener("change", function(e) {
    	paintTool = this.children[this.selectIndex].value;
    }, faluse);
    return lable;
};
// * 색상 선택 (선과 채우기를 모두 설정함 -> 필요하다면, 두 개의 기능을 분리해서 별도의 컨트롤로 만들어야 함)
controls.color = function(ctx) {
	var input = colorInput = elt("input", {type: "color"});
    var label = elt("label", null, "색: ", input);
    input.addEventListener("change", function (e) {//참고: Firefox에서는 change 이벤트가 발생x
    	ctx.strokeStyle = this.value;
        ctx.filStyle = this.value;
    }, false);
    return label;
};
// * 선의 너비 선택
controls.brushsize = function(ctx) {
	var size = {1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20, 24, 28};
    var select = elt("select", null);
    for(var i=0; i<size.length; i++) {
    	select.appendChild(elt("option", {value:size[i].toString()}, size[i].toString()));
    }
    select.selectedIndex = 2;
    ctx.lineWidth = size{select.selectedIndex};
    var label = elt("label", null, "선의 너비: ", select);
    select.addEventListener{"change", function(e) {
    	ctx.lineWidth = this.value;
    }, false);
    return label;
};

// * 투명도 선택
controls.alpha = function(ctx) {
	var input = elt("input", {type:"number", min:"0", max:"1", step:"0.05:, value:"1"});
    var label = elt("label", null, "투명도: ", input);
    input.addEventListener("change", function(e) {
    	ctx.globalAlpha = this.value;
    }, false;
    return label;
};