ECMAScript에는 입출력에 관한 규정이 없어 각각의 JS 실행 환경이 독자적으로 구현하고 있습니다. 따라서 사용하는 실행 환경에 따라 입출력 방법이 달라집니다. 웹브라우저에서 입출력하는 방법과 관련된 사용자 인터페이스인 폼과 같은 HTML 요소와 이 때 필요한 이벤트 처리기 등록 방법, DOM의 기초를 다룹니다. 또한 HTML5 구성요소로 추가된 Canvas로 기초적인 컴퓨터 그래픽스도 다룹니다.
1. 대화상자 표시하기
웹 브라우저에 내장된 대화상자(modal 창)를 활용해 입출력을 해보겠습니다. 웹 브라우저의 전역 객체인 Window에는 대화상자를 표시하는 메서드 alert, prompt, confirm 3가지가 있습니다. 대화상자가 떠 있는 중에는 부모창의 작업이 일시정지 되어 조작할 수 없으며, window. 부분은 생략 가능합니다.
alert() : 경고
alert("토스트는맛있다");
prompt() : 입력칸 표시. 반환값은 문자열
prompt("입력ㄱㄱ");
confirm() : 확인/취소버튼이 있는 대화상자. 확인은 true, 취소는 false 반환
prompt("입력ㄱㄱ");
2. Console
Console 객체의 메서드로 콘솔에 값을 출력할 수 있습니다. 프로그램 동작확인과 디버깅에 자주 사용되며 웹 브라우저와 Node.js를 포함한 다양한 JS 실행 환경에서 사용할 수 있습니다. Console 객체의 주요 메서드는 아래와 같으며, 부모창의 동작을 간섭하지 않습니다.
console.dir : 객체의 대화형 목록 출력
console.error : 오류 메세지 출력
console.info : 메시지 타입 로그를 출력
console.log : 일반 로그를 출력
console.time : 처리 시간 측정용 타이머 시작
console.timeEnd : 처리시간 측정용 타이머를 정지시키고 타이머를 시작한 후 흐른 시간을 밀리초 단위로 출력
console.trace : 스택 트레이스 출력
console.warn : 경고 메시지 출력
Console 객체의 메서드에서 사용할 수 있는 서식 문자열
서식 문자열 | 설명 | 주의사항 |
%o | 객체를 가리키는 하이퍼링크로 변환 | Node.js는 미지원 |
%d | 정수값으로 변환 | Node.js는 숫자로 변환 |
%i | 정수값으로 변환 | Node.js는 미지원 |
%s | 문자열로 변환 | |
%f | 부동소수점 값으로 변환 | Node.js는 미지원 |
3. 이벤트 처리기 등록과 타이머
타이머를 이용하면 슬라이드 쇼나 애니메이션처럼 일정 시간마다 동작을 반복하는 처리를 구현할 수 있습니다.
웹 브라우저에서 동작하는 프로그램은 기본적으로 이벤트 주도형 프로그램 입니다. (event driven program)
이벤트는 단말기와 애플리케이션이 처리할 수 있는 동작이나 사건을 뜻하고, 이벤트 주도형 프로그램은 이벤트가 발생할 때까지 기다렸다가 이벤트가 발생하면 미리 등록해 둔 작업을 수행하는 프로그램입니다.
이벤트 처리기는 이벤트가 발생했을 때 실행되는 함수입니다. 함수를 이벤트 처리기로 등록하는 방법은 HTML 요소의 속성으로 등록하는 법, DOM 요소의 프로퍼티로 등록하는 법, addEventListener 메서드를 사용하는 법이 있습니다.
HTML 요소의 속성으로 등록
<.... onclick="함수">
DOM 요소의 프로퍼티로 등록
DOM(Document Object Model)은 프로그램이 HTML 요소를 조작할 수 있게 하는 인터페이스입니다. DOM의 주요 객체는 Window와 document가 있으며 window 객체는 윈도우 하나 또는 탭 하나를 가리키며 document 객체는 HTML 문서 전체를 가리킵니다. HTML 문서에서 HTML 요소 객체를 가져오거나 새로 만드는 등 HTML 문서 전반에 걸친 기능을 제공합니다. 요소 객체는 HTML 문서의 요소를 가리킵니다.
window.onload = function(){...};
var button = document.getElementById("button");
button.onclick = displayTime;
// 리터럴로
button.onclick = displayTime(){
var d = new Date();
console.log("현재 시각은 " + d.toLocalString()+ "입니다");
};
window.onload에 초기 설정 함수를 이벤트 처리기로 등록하고, getElementById 메서드로 요소 객체를 가져온 후 이벤트 처리기 프로퍼티에 이벤트 처리기로 동작할 함수를 등록합니다. 이벤트 처리기 함수는 함수 리터럴로 직접 대입 가능하며 식별할 수 있는 이름을 붙이면 디버깅에 유리해집니다.
이벤트처리기 제거
console.log(button.onkeydown); //null
button.onclick = null;
이벤트 처리기가 등록되지 않은 이벤트 처리기 프로퍼티에는 기본적으로 null이 담겨 있기에, 등록한 이벤트 처리기를 제거할 때는 null을 대입합니다.
타이머
웹브라우저의 Window 객체에는 setTimeout과 setInterval 메서드가 있습니다. 두 함수는 첫번째 인수로 문자열을 넘길 수도 있는데, 이 때 문자열은 내부적으로 eval() (문자열을 코드로 인식하게 하는 함수)로 평가된 후 실행됩니다.
setTimeout : 일정 시간이 흐른 후 한번만 함수 실행
// 2초 후 날짜 표시
setTimeout(function(){
console.log(new Date()); //실행하려는 함수의 참조
}, 2000); // 지연 시간
// 함수 실행 취소
var timer = setTimeout(function() {...}, 2000);
...
clearTimeout(timer);
setInterval: 일정 시간마다 반복해 함수 실행
setInterval(function(){
console.log(new Date()); //실행하려는 함수의 참조
}, 1000); // 시간 간격
// 1초마다 날짜가 표시
// 함수 실행 취소
var timer = setInterval(function() {...}, 1000);
...
clearTimeout(timer);
4. HTML 요소를 동적으로 읽고쓰기
innerHTML 프로퍼티 활용의 예시 : 스톱워치
<p id = "display">
0.00
</p>
<input id = "start" type="button" value="start">
<input id = "stop" type="button" value="stop">
window.onload = function(){
var startButton = document.getElementById("start");
var stopButton = document.getElementById("stop");
var display = document.getElementById("display");
var startTime, timer;
startButton.onclick = start; // 버튼 활성화
function start(){
startButton.onclick = null; // 버튼 비활성화
stopButton.onclick = stop; // 버튼 활성화
startTime = new Date();
timer = setInterval(function(){
var now = new Date();
display.innerHTML = ((now-startTime)/1000).toFixed(2);
},10);
}
function stop(){
clearInterval(timer);
startButton.onclick = start;
}
};
폼 컨트롤의 입력 값 사용 : 체질량지수 계산하기
각 폼 컨트롤 요소에 대응하는 요소 객체의 프로퍼티는 아래와 같습니다.
요소 | type 속성의 값 | 프로퍼티 | 설명 |
input | number, text 등 | value | 입력된 값을 문자열로 변환한 값 |
checkbox, radio | checked | 항목의 선택 여부를 뜻하는 논리값 | |
select | - | selectedIndex | 선택된 option 요소를 가리키는 0부터 시작하는 번호 |
textarea | - | value | 입력된 문자열 |
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function() {
document.getElementById("button").onclick = function() {
// input 요소에 입력된 데이터를 가져옵니다
var h = parseFloat(document.getElementById("height").value);
var w = parseFloat(document.getElementById("weight").value);
// 체질량지수를 bmi라는 id를 가진 요소(output)에 기록
var bmi = document.getElementById("bmi");
bmi.innerHTML = (w / h / h).toFixed(1); //소수점 자르기
};
};
</script>
</head>
<body>
<p>키: <input type="number" id="height"> m</p>
<p>몸무게: <input type="number" id="weight"> kg</p>
<p>당신의 체질량지수는 <output id="bmi"> ? </output> 입니다</p>
<input type="button" id="button" value="계산">
</body>
</html>
document.write
인수로 받은 문자열을 HTML 문서의 body 요소 안에 출력합니다. 웹 브라우저가 HTML 문서를 해석하는 도중에 document.write를 실행하는 점에 유의합니다. 앞의 예제처럼 body 태그 부분이 해석된 후 스크립트 요소가 해석되지 않습니다. 웹브라우저는 script 요소 안 작업이 끝난 후 </body> 다음 부분을 해석해 추가합니다.
또 이벤트 처리기로 등록한 함수 내에서 document.write를 사용하면 안됩니다. 사용할 경우 이벤트 처리기가 실행되면 HTML 문서 전체 내용이 document.write가 출력한 값으로 바뀝니다. document.write를 사용한 후 호출한 함수에서는 HTML문서를 동적으로 수정할 수 없기에 동적으로 HTML문서를 변경하려면 DOM을 사용해야 합니다.
5. Canvas를 활용한 컴퓨터 그래픽
Canvas는 웹브라우저에서 그래픽을 처리하기 위해 추가된 HTML5 구성요소로, Apple 사가 macOS와 사파리 웹브라우저에서 사용자 인터페이스를 만들기 위해 위젯이나 이미지를 렌더링할 목적으로 개발되었습니다. 주요 웹 브라우저에서 사용가능하며 IE에서는 IE9부터 사용 가능합니다. 캔버스로 2차원그래픽과 WebGL을 사용한 3차원 그래픽을 구현할 수 있습니다. 여기에서는 2차원 컴퓨터 그래픽스 기본을 살펴봅니다.
Canvas의 특징
Canvas의 특징은 즉시 실행형 저수준 API라는 것입니다. 선, 원, 사각형을 그리는 등 기본적인 그리기 기능 (저수준) 만 제공하기에 속도가 빠릅니다. 또한 Canvas의 그리기 명령은 호출 즉시 실행됩니다. 그림 상태를 저장하는 중간 데이터 계층이 없습니다.
다음 예제는 기본적인 사용법을 보여줍니다. canvas 요소는 ctx.canvas 프로퍼티로도 참조 가능합니다.
<canvas id='mycanvas' width='640' height = '400'></canvas>
window.onload = function(){
// canvas 요소 가져오기
var canvas = document.getElementById("mycanvas");
// 렌더링 컨텍스트 가져오기
var ctx = canvas.getContext("2d");
//좌표, 너비, 높이 순
ctx.strokeRect(50, 60, 200, 100);
}
CSS로도 Canvas의 크기를 설정 가능하지만 브라우저에 따라 왜곡, 확대, 축소될 수 있어서 HTML 속성이나 DOM으로 설정합니다. Canvas의 내장 그리기 기능은 사각형 하나뿐이라 나머지 도형은 패스로 정의해서 그립니다.
ctx.strokeRect(10,10,200,150); //사각형 테두리
ctx.fillRect(50, 40, 120, 90); //사각형 채우기
ctx.clearRect(90, 65, 40, 40); //사각형 영역 삭제, 투명화
패스로 그리기
메서드 | |
moveTo | 지정된 좌표로 이동 (선을 그리지 않는 패스만 정의) |
lineTo | 현재 좌표에서 지정된 좌표까지 선을 정의 |
arc | 원호를 정의 |
arcTo | 현재 좌표까지 원호 정의 |
rect | 사각형 정의 |
quadraticCurveTo | 현재 좌표까지 이차 곡선 정의 |
bezierCurveTo | 현재 좌표까지 베지어 곡선 정의 ( n 개의 점으로부터 얻어지는 n-1 차 곡선) |
* 삼각형 그리기
<canvas id='mycanvas' width='640' height = '400'></canvas>
window.onload = function() {
// canvas 요소 가져오기
var canvas = document.getElementById("mycanvas");
// 렌더링 컨텍스트 가져오기
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(60, 10);
ctx.lineTo(110, 100);
ctx.lineTo(10, 100);
ctx.closePath();
ctx.stroke();
// 삼각형 채우기
ctx.beginPath();
ctx.moveTo(60, 120);
ctx.lineTo(110, 210);
ctx.lineTo(10, 210);
ctx.fill();
}
* 원호 그리기
arc(x, y, radius, startAngle, endAngle, anticlockwise)
// 도 단위 각도를 라디안 단위 각도로 변환하는 공식
degress * Mat.PI / 180
window.onload = function() {
// canvas 요소 가져오기
var canvas = document.getElementById("mycanvas");
// 렌더링 컨텍스트 가져오기
var ctx = canvas.getContext("2d");
// 시계 방향
ctx.beginPath();
ctx.arc(100, 100, 80, 30 * Math.PI / 180, 120 * Math.PI / 180, false);
ctx.stroke();
// 시계 반대 방향
ctx.beginPath();
ctx.arc(100, 100, 80, 30 * Math.PI / 180, 120 * Math.PI / 180, true);
ctx.stroke();
}
* 둥근 모서리 그리기
// 둥근 모서리 그리기
window.onload = function() {
// canvas 요소 가져오기
var canvas = document.getElementById("mycanvas");
// 렌더링 컨텍스트 가져오기
var ctx = canvas.getContext("2d");
// 시계 방향
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(200, 10);
ctx.arc(200, 60, 50, -Math.PI / 2, 0, false);
// 같은 결과
// ctx.arcTo(250, 10, 250, 160, 50);
ctx.lineTo(250, 160);
ctx.stroke();
}
* 둥근 모서리 사각형
<canvas id='mycanvas' width='640' height='400'></canvas>
window.onload = function() {
// canvas 요소 가져오기
var canvas = document.getElementById("mycanvas");
// 렌더링 컨텍스트 가져오기
var ctx = canvas.getContext("2d");
strokeRoundedRect(ctx, 10, 10, 100, 80, 10);
strokeRoundedRect(ctx, 150, 10, 100, 80, 30);
}
function strokeRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.stroke();
}
그래픽스 속성 설정하기
사각형을 그리기 전이나 stroke, fill 메서드로 패스를 그리기 전에 설정하면 그림에 반영됩니다. 선의 스타일, 색상 등은 패스 자체에 프로퍼티가 없어 설정할 수 없습니다. strokeStyle과 fillStyle의 프로퍼티 초기값은 검은색입니다. 투명도는 globalAlpha, 두께는 lineWidth으로 설정할 수 있습니다.
ctx.strokeStype = "red"
ctx.fillStyle = "#44ff44";
ctx.globalAlpha = "0.6";
ctx.lineWidth = 2;
패스의 종단점 모양은 lineCap 프로퍼티로 설정하며 설정값은 butt, square, round 총 세가지입니다. 패스 정점은 lineJoin 프로퍼티로 설정하며 miter, round, bevel 세가지 종류가 있습니다.
그림 읽어오기
읽을 수 있는 이미지 리소스는 URL이 가리키는 이미지파일, Canvas로 그린 컴퓨터그래픽스, img 요소 객체, video 요소 객체 등이 있습니다.
drawImage 메서드로 그리려면 이미지를 읽어오는 데 시간이 걸리기 때문에 이미지 객체의 onload 이벤트처리기에 drawImage 호출 함수를 넣습니다. onload는 이벤트처리기를 등록만 하고 함수를 실행하진 않지만, 일반적으로 이미지를 다 읽은 후의 처리를 먼저 등록하고 읽기 작업을 시작하는 편이 좋습니다.
var img = new Image();
img.src = "./cat.jpg";
ctx.drawImage(img, 0, 0);
img.onload = function(){
ctx.drawImage(img, 0, 0);
};
drawImage(image, sx, sy, sw, sh, w, y, width, height)
// 원본이미지에서 특정 부분을 잘라 그리기
픽셀 제어하기
1. getImageData(x, y, width, height)
렌더링 컨텍스트에 렌더링된 이미지 안의 점(x,y)를 왼쪽 위 꼭지점으로 하여 width, height가 너비, 높이인 사각형 영역 안으로 imageData를 가져옵니다.
var imgData = ctx.getImageData(x, y, width, height);
imageData 객체에는 width, height, data 프로퍼티가 있습니다. data 프로퍼티는 이미지의 픽셀 값이 저장된 1차원 배열로 부호 없는 8비트의 정수 배열입니다. RGBA값이 하나씩 RGBA 순서로 저장되어 있습니다. R,G,B,A 값은 부호 없는 0~255 사이의 정수이므로 배열 data의 길이는 4 * width * height입니다.
// imageData의 이미지 안에서 (m, n)번째 픽셀의 RGBA 값 구하기
imageData.getRGBA = function(m, n, i){
return this.data[this.width*4*n + 4*m + i];
};
2. createImageData(width, height)
새로운 imageData 객체를 생성합니다.
- createImageData(width, height) : width, height를 너비 높이로 하는 객체가 생성됩니다. 각 픽셀은 투명한 검은색으로 초기화되어 있습니다.
- createImageData(anotherImageData) : anotherImageData 와 같은 크기의 이미지를 담을 수 있는 imageData 객체를 생성합니다.
3. putImageData(imageData, x, y)
ImageData 객체의 이미지를 렌더링 컨텍스트에 그립니다. 인수 x와 y는 그릴 이미지의 왼쪽 위 모서리의 좌표입니다.
'Javascript' 카테고리의 다른 글
모던 자바스크립트 입문 : 8. 함수 (0) | 2020.07.16 |
---|---|
모던 자바스크립트 입문 : 7. 제어 구문 (0) | 2020.07.07 |
Local Storage (로컬 스토리지) 를 이용한 데이터 저장 (1) | 2020.06.26 |
[JS] 클래스 제어하기 - classList 그리고 IE (0) | 2020.06.23 |
[JS] 버튼의 포커스이벤트 유지 (활성화 유지) 를 고민해보면서 (0) | 2020.06.18 |