Getting started with WebGL - Web API

Getting started with WebGL
==========================

* 다음 »

WebGL은 플러그인을 사용하지 않고 OpenGL ES 2.0 기반 API를 이용하여 브라우저의 HTML `canvas`에 렌더링하여 3D 웹 콘텐츠 제작을 가능하게 합니다. WebGL 프로그램은 컴퓨터의 그래픽 처리 장치(GPU)에서 실행되는 JavaScript나 특수 효과(셰이더 코드)코드로 구성됩니다. WebGL 요소들은 다른 HTML 요소들과 혼합될 수 있고 페이지나 페이지 배경의 다른 부분과 합성될 수 있습니다.

이 문서는 기본 WebGL 기본 사항을 소개합니다. 이 문서에서는 3D 그래픽에 관련된 수학적 이해를 이미 이해하고 있다고 간주하고 OpenGL 자체에 대하여 설명하지 않을 것입니다.

3D 렌더링 준비
---------

WebGL을 사용하여 3D 렌더링을 하는 데 첫 번째로 필요한 것은 캔버스입니다. 아래 HTML 코드는 canvas를 만들고 사용할 WebGL 컨텍스트를 초기화하는 onload 이벤트 핸들러를 지정합니다.

```html
<body onload="start()">
  <canvas id="glcanvas" width="640" height="480">
    Your browser doesn't appear to support the HTML5
    <code><canvas></code> element.
  </canvas>
</body>
```

### WebGL 컨텍스트 준비

JavaScript 코드에서 `start()` 함수는 문서가 다 불러와지면 호출됩니다. 이 함수의 기능은 WebGL 컨텍스트를 설정하고 콘텐츠 렌더링을 시작하는 것입니다.

```js
var gl; // A global variable for the WebGL context

function start() {
  var canvas = document.getElementById("glcanvas");

  gl = initWebGL(canvas); // Initialize the GL context

  // Only continue if WebGL is available and working

  if (gl) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0); // Set clear color to black, fully opaque
    gl.enable(gl.DEPTH\_TEST); // Enable depth testing
    gl.depthFunc(gl.LEQUAL); // Near things obscure far things
    gl.clear(gl.COLOR\_BUFFER\_BIT | gl.DEPTH\_BUFFER\_BIT); // Clear the color as well as the depth buffer.
  }
}
```

첫 번째 할 일은 canvas에 대한 참조를 얻는 것 입니다. canvas라는 변수에 지정합니다. 당연히 canvas를 반복적으로 참조할 필요는 없고 전역 변수로 저장하는 것은 피해야 합니다. 지역 변수나 객체의 필드 멤버로 참조해야 됩니다.

캔버스가 있으면 `initWebGL()`이라는 함수를 호출할 수 있습니다. 이 함수는 일시적으로 정의되고 WebGL 컨텍스트를 초기화하는 일을 합니다.

만약 컨텍스트가 성공적으로 초기화 되면 gl은 이를 참조합니다. 이번 예제에서는 검은색 투명 색상을 설정하면 컨텍스트를 그 색상으로 지정합니다. 그 다음 컨텍스트는 설정 매개변수로 설정됩니다. 예제에서는 깊이 테스트가 가능하고 가까운 물체가 멀리 떨어저 있는 물체를 가리는 것을 지정합니다.

코드에서 초기화를 전달하는 목적은 우리가 하려는 것 전부 입니다. 잠시 후 실제로 무언가를 어떻게 시작하는가 알아볼 것입니다.

### WebGL 컨텍스트 생성

`initWebGL()` 함수는 다음과 같습니다.

```js
function initWebGL(canvas) {
  gl = null;

  try {
    // Try to grab the standard context. If it fails, fallback to experimental.
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  } catch (e) {}

  // If we don't have a GL context, give up now
  if (!gl) {
    alert("Unable to initialize WebGL. Your browser may not support it.");
    gl = null;
  }

  return gl;
}
```

캔버스에서 WebGL 컨텍스트를 얻기 위해 canvas로 "webgl"이라고 불리는 컨텍스트를 요청할 것입니다. 만약에 실패한다면 "experimental-webgl"이라는 이름으로 시도할 것입니다. 만약 이마저도 실패한다면 사용자에게 사용 중인 브라우저가 WebGL을 지원하지 않는다는 경고를 출력할 것입니다. 이게 전부입니다. 이 시점에서 gl은 null(WebGL 컨텍스트를 이용할 수 없다는 의미)이거나 렌더링할 WebGL 컨텍스트를 참조할 것입니다.

**참고:** experimental-webgl이라는 이름은 사양 개발 시 사용되는 컨텍스트를 위한 일시적인 이름입니다. webgl은 사양이 확정되면 사용됩니다.

이 시점에서 이 코드는 WebGL 컨텍스트가 성공적으로 초기화하는 데 충분한 코드입니다. 이 코드를 통해 검정 박스 형태의 빈 공간이 형성되며, 여기에 콘텐츠를 생성할 기본 준비가 되었습니다.

여기를 클릭하여 예제를 확인하세요. 브라우저가 WebGL을 호환한다면 실행될 것 입니다.

### WebGL 컨텍스트 크기 조정

이제 새로운 WebGL 컨텍스트는 새로운 컨텍스트 인스턴스를 얻었습니다. 그리고 CSS 없이 캔버스 요소의 height와 width로 뷰포트의 해상도를 설정합니다. 캔버스 요소의 스타일 편집하면 출력되는 크기를 변경될 것이지만 렌더링 해상도는 변경되지 않습니다. 또한 컨텍스트가 생성된 후 캔버스 요소의 width와 height 속성을 편집하면 그려지는 픽셀 수를 변경할 수 없습니다. WebGL 렌더의 해상도를 변경하려면 사용자가 캔버스 문서 전체 창 크기를 조정하거나 앱에서 그래픽 설정을 조정할 수 있게 하길 원할 것입니다. WebGL 컨텍스트 viewport() 함수가 변경할 수있는 것으로 알려져 있습니다.

렌더링된 WebGL 컨텍스트의 해상도를 수정하려면 위에 나오는 gl과 canvas 변수를 사용해야 됩니다.

```js
gl.viewport(0, 0, canvas.width, canvas.height);
```

캔버스는 CSS 스타일과 다른 해상도로 렌더링되어질 때 화면에서 차지하는 크기를 볼 것입니다. CSS로 크기를 조정하면 낮은 해상도에서 렌더링하거나 브라우저 화면을 확대할 때 자원을 절약하는 데 유용합니다. 축소는 슈퍼샘플 안티에일리어싱(SSAA) 효과를 사용할 때 가능합니다. (많은 성능 비용이 발생하고 작은 결과이기는 하지만) 아래에 참고 사항을 살펴보는 것이 가장 좋습니다.

* WebGL에 대한 소개 - DEV.OPERA에 있는 Luz Caballero가 작성한 문서입니다. 이 문서는 WebGL이 무엇인지, 어떻게 작동되는지, 렌더링 파이프라인 개념에 대하여 설명하고 몇 가지 WebGL 라이브러리에 대하여 소개합니다.
* 현대 OpenGL에 대한 소개 - Joe Groff가 작성한 OpenGL에 대한 좋은 문서 시리즈입니다. 조는 명확하게 역사를 가지고 OpenGL의 중요한 그래픽 파이프라인 개념에 대해 소개하고 몇 가지 데모를 통해 OpenGL이 어떻게 작동되는지 설명하기 위해 예제들을 제공합니다. 만약 OpenGL에 대한 개념이 안 잡혀 있다면 시작하기 좋은 장소가 될 것입니다.
* 다음 »


WebGL을 사용한 객체 애니메이션 - Web API

WebGL을 사용한 객체 애니메이션
===================

* « 이전
* 다음 »

앞 단원에서의 예제 코드는 정지되어 있는 것처럼 보이지만 사실은 15밀리초마다 한 번 씩 WebGL 장면(Scene)을 다시 그리도록 설정되어 있습니다. 그려질 객체의 상태가 계속 똑같기 때문에, 똑같은 객체를 15밀리초마다 계속 다시 그리고 있는 것입니다. 이제 우리가 그린 정사각형을 실제로 움직이게 만들어 보겠습니다.

이번 예제에서는 2차원 정사각형을 3차원으로 회전시키고 이동시켜 보겠습니다. 3차원을 이루는 X, Y, Z축 방향 모두를 중심으로 회전시켜 보면, 우리가 그린 것은 2차원 정사각형이지만 3차원 공간 안에 존재한다는 것을 실감할 수 있을 것 입니다.

정사각형 회전시키기
----------

먼저 정사각형을 회전시켜 보겠습니다. 회전 시키려면 회전 상태(회전량)를 저장할 변수가 필요합니다:

```js
var squareRotation = 0.0;
```

이제 정사각형을 그릴 때 회전을 반영하도록 `drawScene()` 함수를 수정해야 합니다. 정사각형을 처음에 그려질 위치로 이동시키고 나서 다음과 같이 회전을 적용합니다:

```js
mvPushMatrix();
mvRotate(squareRotation, [1, 0, 1]);
```

현재 상태의 모델-뷰 행렬을 저장하고, X축과 Z축을 기준으로 `squareRotation` 만큼 행렬을 회전시킵니다.

정사각형을 그리고 난 후에 모델-뷰 행렬을 원상태로 복구합니다:

```js
mvPopMatrix();
```

모델-뷰 행렬을 복구하는 이유는 이 회전이 다른 객체에 영향을 미치는 것을 예방하기 위해서 입니다.

실제로 애니메이션 효과가 나타나도록 하려면 `squareRotation` 값을 시간이 지남에 따라 계속 변경해주는 코드를 추가해야 합니다. `lastSquareUpdateTime`이라는 변수에 마지막으로 다시 그렸던 시각을 저장하고, 다음과 같은 코드를 `drawScene()`에 추가합니다:

```js
var currentTime = new Date().getTime();
if (lastSquareUpdateTime) {
  var delta = currentTime - lastSquareUpdateTime;

  squareRotation += (30 \* delta) / 1000.0;
}

lastSquareUpdateTime = currentTime;
```

이 코드는 마지막으로 `squareRotation`의 값을 변경한 시각과 현재 시각과의 차이를 이용해서 회전량을 나타내는 `squareRotation`의 값을 결정 합니다.

정사각형 이동시키기
----------

정사각형을 그리기 전에 위치값을 변경하면 정사각형을 이동시킬 수 있습니다. 이번 예제에서는 학습을 목적으로 아주 기초적인 애니메이션을 구현해봅니다. 실전에서 이런 식으로 애니메이션을 구현하면 사람들에게 사랑받지 못할 것입니다.

X, Y, Z 각 축별 오프셋(offset) 값을 새 변수에 저장합니다:

```js
var squareXOffset = 0.0;
var squareYOffset = 0.0;
var squareZOffset = 0.0;
```

축 별 위치 변동값을 다음과 같이 각기 다른 값으로 지정합니다:

```js
var xIncValue = 0.2;
var yIncValue = -0.4;
var zIncValue = 0.3;
```

이제 위치 변동값을 계산하는 코드를 위에서 구현한 회전량 계산 코드 바로 아래에 추가합니다:

```js
squareXOffset += xIncValue \* ((30 \* delta) / 1000.0);
squareYOffset += yIncValue \* ((30 \* delta) / 1000.0);
squareZOffset += zIncValue \* ((30 \* delta) / 1000.0);

if (Math.abs(squareYOffset) > 2.5) {
  xIncValue = -xIncValue;
  yIncValue = -yIncValue;
  zIncValue = -zIncValue;
}
```

마지막으로 다음 코드를 `drawScene()` 함수에 추가합니다:

```js
mvTranslate([squareXOffset, squareYOffset, squareZOffset]);
```

이제 정사각형이 화면에서 좌, 우, 위, 아래, 앞, 뒤로 완전히 자기멋대로 움직이면서 회전하는 것을 볼 수 있습니다. 어찌보면 바탕화면 보호기 같기도 합니다.

WebGL이 지원되는 브라우저라면, 여기에서 실제 동작하는 예제를 확인할 수 있습니다.

추가적인 행렬 연산
----------

아래의 예제는 스택을 사용하는 두 개의 push, pop 루틴과, 주어진 각도만큼 회전시키는 행렬을 포함하는 몇 가지 추가적인 행렬 연산을 사용합니다. 한 번 참고하시기 바랍니다:

```js
var mvMatrixStack = [];

function mvPushMatrix(m) {
  if (m) {
    mvMatrixStack.push(m.dup());
    mvMatrix = m.dup();
  } else {
    mvMatrixStack.push(mvMatrix.dup());
  }
}

function mvPopMatrix() {
  if (!mvMatrixStack.length) {
    throw "Can't pop from an empty matrix stack.";
  }

  mvMatrix = mvMatrixStack.pop();
  return mvMatrix;
}

function mvRotate(angle, v) {
  var inRadians = (angle \* Math.PI) / 180.0;

  var m = Matrix.Rotation(inRadians, $V([v[0], v[1], v[2]])).ensure4x4();
  multMatrix(m);
}
```

이 루틴은 예전에 Vlad Vukićević가 작성했던 예제를 참고하여 만들었습니다.

* « 이전
* 다음 »


WebGL에서 텍스쳐 사용하기 - Web API

WebGL에서 텍스쳐 사용하기
================

* « 이전
* 다음 »

앞 단원의 예제에서 회전하는 3차원 정육면체를 만들어봤습니다. 이번에는 정육면체의 각 면에 단색으로 색을 칠하는 대신에 텍스쳐를 입혀 보겠습니다.

텍스쳐 로딩
------

가장 먼저 해야할 일은 텍스쳐를 읽어오는 것입니다. 이번 예제에서는 동일한 하나의 텍스쳐를 회전하는 정육면체의 6개의 면에 입혀볼 것입니다. 여러개의 텍스쳐를 각 면에 입힌다고 해도 하나를 입히는 것과 동일한 방법을 적용하면 됩니다.

**참고:** 텍스쳐를 외부에서 읽어올 때는 크로스 도메인 규칙(cross-domain rules) (en-US)에 유의해야 합니다. CORS(Cross Origin Resource Sharing)승인을 받을 수 있는 도메인에 있는 텍스쳐만 읽어올 수 있습니다. 자세한 내용은 크로스 도메인 텍스쳐(Cross-domain textures) (en-US)를 참고하세요.

텍스쳐를 읽어오는 코드는 다음과 같습니다:

```js
function initTextures() {
  cubeTexture = gl.createTexture();
  cubeImage = new Image();
  cubeImage.onload = function () {
    handleTextureLoaded(cubeImage, cubeTexture);
  };
  cubeImage.src = "cubetexture.png";
}

function handleTextureLoaded(image, texture) {
  gl.bindTexture(gl.TEXTURE\_2D, texture);
  gl.texImage2D(gl.TEXTURE\_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED\_BYTE, image);
  gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MAG\_FILTER, gl.LINEAR);
  gl.texParameteri(
    gl.TEXTURE\_2D,
    gl.TEXTURE\_MIN\_FILTER,
    gl.LINEAR\_MIPMAP\_NEAREST,
  );
  gl.generateMipmap(gl.TEXTURE\_2D);
  gl.bindTexture(gl.TEXTURE\_2D, null);
}
```

`initTextures()` 루틴은 GL의 `createTexture()` 함수를 호출해서 GL의 텍스쳐 객체인 `cubeTexture`를 생성하는 걸로 시작됩니다. 그리고 `Image` 객체를 생성해서 텍스쳐로 사용하기 위해 로딩한 이미지 파일을 `Image` 객체에 저장합니다. `handleTextureLoaded()` 콜백 루틴은 이미지 로딩이 완료되면 실행됩니다.

텍스쳐를 실질적으로 생성하려면, 앞에서 새로 생성한 텍스쳐 객체를 `gl.TEXTURE_2D`에 바인딩해야 합니다. 그리고 나서 이미지 데이터가 로딩된 이미지 객체를 `texImage2D()`에 전달하여 호출하면, 이미지 데이터가 텍스쳐에 쓰여(write) 집니다.

**참고:** 텍스쳐의 너비와 높이는 **거의 대부분**의 상황에서 2의 거듭제곱 픽셀(1, 2, 4, 8, 16, 32, ...)이어야 합니다. 예외인 경우에 대해서는 아래의 *"*크기가 2의 거듭제곱 픽셀이 아닌 텍스쳐*"를 참고하세요.*

그 다음 두 라인은 텍스쳐를 위한 필터링을 준비합니다. 이 필터링은 이미지 크기가 변경될 때 이미지가 필터되는 방식을 제어합니다. 여기에서는 이미지를 확대할 때 선형 필터링을 사용하고, 이미지를 축소할 때 mipmap을 사용합니다. generateMipMap()을 호출해서 mipmap이 만들어지면 gl.TEXTURE\_2D에 null을 바인딩시켜서, 텍스쳐를 다룰 준비가 끝났다는 것을 WebGL에게 알려줍니다.

### 크기가 2의 거듭제곱이 아닌 텍스쳐

일반적으로 너비와 높이가 2의 거듭제곱인 텍스쳐를 사용하는 것이 가장 이상적입니다. 왜냐하면 2의 거듭제곱인 텍스쳐는 비디오 메모리에 효율적으로 저장될 수 있고, 어떤 방식으로 사용되어야만 한다는 제약이 없기 때문입니다. 예술가들이 이미 작성한 텍스쳐는 너비와 높이가 2의 거듭제곱이 되도록 크기를 맞춰줘야 하며, 가능하다면 아예 만들때부터 2의 거듭제곱으로 만드는 것이 좋습니다. 너비와 높이는 2의 거듭제곱인 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 또는 2048 픽셀이어야 합니다. 전부는 아니지만 많은 디바이스가 4096 픽셀도 지원하고 있으며, 어떤 디바이스는 8192 픽셀 이상을 지원하기도 합니다.

2의 거듭제곱인 텍스쳐를 사용하기 곤란한 상황도 있을 수 있습니다. 텍스쳐의 소스가 되는 이미지를 써드파티에서 구한 것이라면, WebGL에 전달하기 전에 HTML5 캔버스를 이용해서 이미지 크기를 2의 거듭제곱으로 수정하는 것이 좋습니다. 이 때 UV 좌표값도 함께 조정해야 합니다.

2의 거듭제곱이 아닌(NPOT, Non Power Of Two) 텍스쳐를 **꼭 써야만 하는** 상황도 있을 것입니다. WebGL은 NPOT 텍스쳐도 제한적으로 지원합니다. 텍스쳐의 크기가 모니터 해상도와 똑같아야만 한다거나, 위의 단락에서 언급한 것처럼 2의 거듭제곱으로 수정하는 일이 단순히 귀찮을 때는 NPOT 텍스쳐가 유용할 수 있습니다. 하지만 NPOT 텍스쳐에는 제약 사항이 있습니다. NPOT 텍스쳐는 **mipmapping을 할 수 없으며**, 타일(tile) 또는 감싸기(wrap) 처럼 **"반복"하는 방식으로 사용할 수 없습니다**.

몇 개의 벽돌 이미지를 타일링 해서 벽돌로 된 벽을 만드는 것이 텍스쳐 반복의 한 사례 입니다.

`bindTexture()`를 이용해서 텍스쳐를 생성할 때, `texParameteri()` 메서드로 mipmapping과 UV 반복을 비활성화 시킬 수 있습니다. 이 비활성화를 통해 mipmapping, UV 감싸기, UV 타일링을 포기하고, 디바이스가 텍스쳐를 어떻게 처리할지 결정할 수 있는 제어권도 포기하는 대신 NPOT 텍스쳐를 사용할 수 있게 됩니다.

```js
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MIN\_FILTER, gl.LINEAR); //gl.LINEAR 대신에 gl.NEAREST도 허용되지만, 둘 다 mipmap 될 수 없다.
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_S, gl.CLAMP\_TO\_EDGE); //s좌표계 감싸기(반복) 방지
gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_T, gl.CLAMP\_TO\_EDGE); //t좌표계 감싸기(반복) 방지
```

`texParameteri()` 메서드에 위와 같은 파라미터를 전달함으로써, WebGL을 지원하는 디바이스는 어떤 해상도의 텍스쳐든 처리할 수 있는 최대한의 해상도까지 자동으로 처리할 수 있게 됩니다. 위와 같은 설정을 해주지 않으면 WebGL은 NPOT 텍스쳐를 처리하지 못하고 `rgba(0, 0, 0, 1)`인 검은색을 반환합니다.

면에 텍스쳐 입히기
----------

이제 텍스쳐 읽어오기는 완료되었고, 텍스쳐도 사용할 준비가 되어 있습니다. 하지만 텍스쳐를 사용하기 전에 텍스쳐의 좌표와 정육면체의 면의 정점을 매핑 시켜줘야 합니다. 이를 위해 `initBuffers()` 함수 안에 있던 정육면체 각 면의 색상을 설정하는 내용을 모두 아래와 같은 코드로 대체합니다.

```js
cubeVerticesTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY\_BUFFER, cubeVerticesTextureCoordBuffer);

var textureCoordinates = [
  // 앞
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  // 뒤
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  // 위
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  // 아래
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  // 오른쪽
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  // 왼쪽
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
];

gl.bufferData(
  gl.ARRAY\_BUFFER,
  new WebGLFloatArray(textureCoordinates),
  gl.STATIC\_DRAW,
);
```

먼저 각 면의 텍스쳐 좌표를 저장할 GL 버퍼를 생성하고, 텍스쳐 좌표 배열에 바인딩 합니다.

`textureCoordinates` 배열은 정육면체 각 면의 정점에 해당하는 텍스쳐 좌표를 정의합니다. 텍스쳐 좌표값의 범위는 0.0 에서 1.0 사이라는 점을 기억해 주십시오. 텍스쳐 좌표의 너비값과 높이값은 실제 너비값이나 높이값과 관계 없이 언제나 0.0 에서 1.0 사이의 값으로 정규화(normalize) 됩니다.

텍스쳐 매핑 배열 설정이 끝나고 배열을 버퍼에 전달하면 GL이 텍스쳐 데이터를 사용할 수 있게 됩니다.

**참고:** Note: WebKit 기반의 브라우저에서는 `WebGLFloatArray` 대신에 `Float32Array를 사용해야 합니다.`

셰이더 수정
------

셰이더 프로그램과 셰이더를 초기화하는 코드들도 단색 색상 대신 텍스쳐를 사용할 수 있도록 수정해야 합니다.

먼저 `initShaders()` 안에 있는 아주 단순한 변경 사항을 알아 봅시다:

```js
textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(textureCoordAttribute);
```

정점 컬러 attribute 변수를 설정하던 코드가, 각 정점의 텍스쳐 좌표값을 설정하는 코드로 대체 되었습니다.

### 정점 셰이더

다음으로 색상 데이터를 읽어오던 정점 셰이더를 텍스쳐 좌표를 읽어오도록 수정해야 합니다.

```html
<script id="shader-vs" type="x-shader/x-vertex">
 attribute vec3 aVertexPosition;
 attribute vec2 aTextureCoord;

 uniform mat4 uMVMatrix;
 uniform mat4 uPMatrix;

 varying highp vec2 vTextureCoord;

 void main(void) {
 gl\_Position = uPMatrix \* uMVMatrix \* vec4(aVertexPosition, 1.0);
 vTextureCoord = aTextureCoord;
 }
</script>
```

정점 색상 정보를 읽어오는 대신에 텍스쳐 좌표값을 읽어와서 설정한다는 점이 키 포인트 입니다. 위와 같이 정점과 텍스쳐 좌표값을 매핑하면, 각 정점이 텍스쳐의 어느 지점에 해당 하는지 알려줄 수 있습니다.

### 프래그먼트 셰이더

마찬가지로 프래그먼트 셰이더도 수정해야 합니다:

```html
<script id="shader-fs" type="x-shader/x-fragment">
 varying highp vec2 vTextureCoord;

 uniform sampler2D uSampler;

 void main(void) {
 gl\_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
 }
</script>
```

이렇게 하면 프래그먼트의 색상을 정하기 위해 직접 프래그먼트에 색상값을 할당하지 않고, 샘플러(sampler)가 판단하기에 프래그먼트의 위치에 가장 잘 맞아 떨어진다고 여겨지는 **텍셀**(**texel, 텍스쳐 내부에 있는 픽셀**)값에 따라서 프래그먼트의 색상값을 계산해냅니다.

텍스쳐를 입힌 정육면체 그리기
----------------

텍스쳐를 입힌 상태를 더 명확하게 확인할 수 있도록, 앞 단원의 예제에 포함되어 있던 정육면체의 이동을 제거한 것을 제외하면 drawScene() 함수의 수정은 간단합니다.

정점에 색상을 매핑하던 코드를 다음과 같이 면에 텍스쳐를 매핑하는 코드로 대체합니다:

```js
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE\_2D, cubeTexture);
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);
```

GL은 32개의 텍스쳐 레지스터를 제공합니다. 그 중 첫번째 레지스터는 `gl.TEXTURE0` 입니다. 텍스쳐를 사용하기 위해 전에 읽어온 텍스쳐를 gl.TEXTURE0에 바인딩하고, 셰이더 샘플러를 셰이더 프로그램에 명시되어 있는 `uSampler`로 설정합니다.

이제 앞 단원의 예제보다 더 보기 좋게 회전하는 정육면체를 볼 수 있을 것입니다. WebGL을 지원하는 브라우저라면 여기에서 실제 동작하는 예제를 확인할 수 있습니다.

* « 이전
* 다음 »


WebGL에서의 텍스쳐 애니메이션 - Web API

WebGL에서의 텍스쳐 애니메이션
==================

* « 이전

앞 단원에서는 정적인 텍스쳐를 사용한 예제를 만들어 봤었는데, 이번에는 Ogg 비디오 파일을 이용해서 텍스쳐에 애니메이션 효과를 적용해 보겠습니다. 사실 만들기 상당히 쉽지만, 그래도 보는 재미는 쏠쏠하니까 한 번 만들어 보겠습니다. 텍스쳐를 구성할 소스로 어떤 종류의 데이터(예를 들면 `<canvas>`와 같은)를 쓰더라도 코드는 비슷할 것입니다.

텍스쳐를 구성할 비디오 로딩
---------------

가장 먼저 할 일은 비디오 프레임을 조회하는데 사용할 `<video>` 요소를 생성하는 것입니다:

```js
<video id="video">
  Your browser doesn't appear to support the HTML5 <code><video></code>{" "}
  element.
</video>
```

>
> 역자 주 : 실제 예제 소스 코드를 보면 비디오 태그가 위와 같이 id 속성만 있는 것이 아니라 아래와 같이 src 속성과 autoplay 속성도 추가되어 있습니다.
>
>
> js
> ```js
> <video id="video" src="Firefox.ogv" autoplay>
>   Your browser doesn't appear to support the HTML5 <code><video></code>{" "}
>   element.
> </video>
>
> ```
>
>

위 코드는 'Firefox.ogv" 비디오 파일을 재생할 `<video>` 요소를 생성합니다. 다음과 같은 CSS 코드를 작성해서 비디오가 자동으로 표시되지 않도록 합니다:

```css
video {
  display: none;
}
```

이제 JavaScript 코드를 살펴보겠습니다. `start()` 함수에 비디오 요소에 대한 참조를 가져오는 코드를 추가합니다:

```js
videoElement = document.getElementById("video");
```

`setInterval()`을 이용해서 `drawScene()`을 주기적으로 호출하던 코드를 아래의 코드로 대체합니다:

```js
videoElement.addEventListener("canplaythrough", startVideo, true);
videoElement.addEventListener("ended", videoDone, true);
```

비디오 요소의 src 속성으로 비디오 파일의 위치를 지정해서 비디오 로딩을 시작합니다. FIXME (이 글의 작성자의 한 사람인 bjacob에게) : `preload="auto"`를 여기에서 명시하지 않으면 Firefox에서는 `canplaythrough` 이벤트가 발생되지 않음. 크롬에서는 `preload="auto"` 지정 여부와 관계없이 비디오 로딩 시작.

>
> 역자 주 : 예제에 보면 아래와 같이 JavaScript에서 preload나 src를 명시하지 않고, 위의 역자 주에 있는 것처럼 비디오 태그 내에 속성값으로 기술하고 있으므로, 바로 위 문단과 아래의 코드는 무시해도 좋을 것 같습니다.
>
>
>

```js
video.preload = "auto";
videoElement.src = "Firefox.ogv";
```

비디오 재생 시 끊김이 없도록 충분히 버퍼링 해둔 후에 애니메이션을 시작하는 것이 좋습니다. 전체 비디오가 끊김 없이 재생될 수 있을만큼 충분한 데이터가 버퍼링 된 후에 비디오의 재생이 시작되도록 아래와 같이 canplaythrough 이벤트에 대한 리스너인 `startVideo()`를 추가합니다:

```js
function startVideo() {
  videoElement.play();
  intervalID = setInterval(drawScene, 15);
}
```

위 코드는 단순히 비디오 재생을 시작하고, 정육면체의 렌더링을 처리하는 `drawScene()` 함수를 `setInterval()` 함수를 이용해서 주기적으로 호출합니다.

비디오가 끝날 때 방생하는 `ended` 이벤트에 대한 리스너도 추가해서, 비디오 재생이 끝나면 불필요하게 CPU 시간을 잡아먹지 않도록 애니메이션을 중단시킵니다.

```js
function videoDone() {
  clearInterval(intervalID);
}
```

`videoDone()` 함수는 단순히 `window.clearInterval()` (en-US) 함수를 호출해서 애니메이션을 업데이트하는 `drawScene()` 함수의 호출을 중단시킵니다.

비디오 프레임을 텍스쳐로 사용하기
------------------

비디오 로딩과 재생에 대한 처리를 마치면, `initTexture()` 함수의 내용을 변경해야 합니다. 이번에는 이미지를 로딩하는 대신 비어있는 텍스쳐 객체를 생성하고, 텍스쳐 객체를 나중에 사용할 수 있도록 필터링을 설정하기만 하면 되므로, `initTexture()` 함수의 내용이 앞 단원의 예제보다 조금 더 간단해집니다:

```js
function initTextures() {
  cubeTexture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE\_2D, cubeTexture);
  gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MAG\_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_MIN\_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_S, gl.CLAMP\_TO\_EDGE);
  gl.texParameteri(gl.TEXTURE\_2D, gl.TEXTURE\_WRAP\_T, gl.CLAMP\_TO\_EDGE);
}
```

가장 중요한 텍스쳐의 업데이트를 담당하는 `updateTexture()` 함수는 다음과 같습니다:

```js
function updateTexture() {
  gl.bindTexture(gl.TEXTURE\_2D, cubeTexture);
  gl.pixelStorei(gl.UNPACK\_FLIP\_Y\_WEBGL, true);
  gl.texImage2D(
    gl.TEXTURE\_2D,
    0,
    gl.RGBA,
    gl.RGBA,
    gl.UNSIGNED\_BYTE,
    videoElement,
  );
}
```

위와 같은 코드를 전에 본 적이 있을 것입니다. `texImage2D()` 를 호출할 때 `Image` 객체를 전달하지 않고 `<video>` 요소를 전달한다는 것만 제외하면, 앞 단원에서 사용했던 `handleTextureLoaded()` 루틴과 거의 똑같습니다. 현재의 프레임을 추출해서 텍스쳐로 사용하는 것은 WebGL이 알아서 처리합니다.

`drawScene()` 함수에는 맨 앞에 updateTexture()를 추가합니다. `drawScene()` 함수에 의해 장면을 다시 그릴 때마다 `updateTexture()` 이 호출됩니다.

이제 완성입니다! WebGL을 지원하는 브라우저라면 여기에서 실제 작동하는 예제를 확인할 수 있습니다.

참고 자료
-----

* Firefox에서 audio와 video 사용하기 (en-US)
* « 이전


WebGL에서 셰이더를 사용하여 색상 적용하기 - Web API

WebGL에서 셰이더를 사용하여 색상 적용하기
=========================

* « 이전
* 다음 »

앞 단원의 예제에서 정사각형을 그려봤으니, 이제 정사각형에 색을 칠해 보겠습니다. 셰이더를 조금 수정하면 색을 칠할 수 있습니다.

정점에 색상 적용
---------

GL(Graphic Library)에서 객체는 정점의 집합으로 구성되며, 각 정점은 위치값과 색상값을 가지고 있습니다. 기본적으로 정점이 아닌 모든 픽셀의 색상값은 선형 보간법(linear interpolation)을 이용해서 계산됩니다. 색상 뿐 아니라 위치를 포함한 다른 모든 속성들도 마찬가지로 선형 보간법으로 계산됩니다. 색상의 경우 선형 보간법을 통해 보간되면 자연스럽게 부드러운 그라데이션(gradation)이 형성됩니다. 앞 단원에서는 정점 셰이더에서 정점에 아무런 색상도 적용하지 않았습니다. 대신에 프래그먼트 셰이더에서 각 픽셀에 흰색을 적용했기 때문에 전체 사각형이 흰색으로 그려질 수 있었습니다.

이제 정사각형의 각 꼭지점에 빨간색, 파란색, 녹색, 흰색을 적용해 보겠습니다. 정점이 아닌 픽셀들은 선형 보간에 의해 그라데이션이 형성됩니다. 먼저 네 개의 정점에 색을 지정하겠습니다. 정점에 색을 지정하려면 정점의 색상값을 가진 배열을 만들고, 이 배열을 WebGL 버퍼에 저장해야 합니다. `initBuffers()` 함수에 아래의 내용을 추가합니다:

```js
  var colors = [
    1.0,  1.0,  1.0,  1.0,    // 흰색
    1.0,  0.0,  0.0,  1.0,    // 빨간색
    0.0,  1.0,  0.0,  1.0,    // 녹색
    0.0,  0.0,  1.0,  1.0     // 파란색
  ];

  squareVerticesColorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, squareVerticesColorBuffer);
  gl.bufferData(gl.ARRAY\_BUFFER, new Float32Array(colors), gl.STATIC\_DRAW);
}
```

먼저 정점에 지정할 색상값을 가진 JavaScript 배열을 생성합니다. 4개의 원소가 하나의 색을 나타내며, 정사각형의 4개의 꼭지점에 흰색, 빨간색, 파란색, 녹색이 지정되도록 값을 정합니다. 이 색상 정보 배열을 저장하기 위해 새로운 WebGL 버퍼를 생성하고, 배열은 부동소수점 형식으로 WebGL의 버퍼에 저장합니다.

이 색상 정보를 실제 렌더링에 사용하려면, 컬러 버퍼에서 색상 정보를 읽을 수 있도록 정점 셰이더를 수정해야 합니다:

```html
<script id="shader-vs" type="x-shader/x-vertex">
 attribute vec3 aVertexPosition;
 attribute vec4 aVertexColor;

 uniform mat4 uMVMatrix;
 uniform mat4 uPMatrix;

 varying lowp vec4 vColor;

 void main(void) {
 gl\_Position = uPMatrix \* uMVMatrix \* vec4(aVertexPosition, 1.0);
 vColor = aVertexColor;
 }
</script>
```

수정 전의 소스와 가장 많이 달라진 점은, 색상 배열에서 읽은 값을 토대로 각 정점에 색상값을 지정했다는 점입니다.

프래그먼트에 색상 입히기
-------------

앞 단원에서 사용했던 프래그먼트 셰이더를 다시 한 번 보겠습니다:

```html
<script id="shader-fs" type="x-shader/x-fragment">
 void main(void) {
 gl\_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
 }
</script>
```

프래그먼트에 색상 입히기는 아주 쉽습니다. 위와 같이 프래그먼트 셰이더 내에서 흰색을 지정해주는 대신 단순히 `vColor` 변수에서 값을 읽어오도록 수정하기만 하면, 각 픽셀 별로 보간된 색상값을 구할 수 있습니다:

```html
<script id="shader-fs" type="x-shader/x-fragment">
 varying lowp vec4 vColor;

 void main(void) {
 gl\_FragColor = vColor;
 }
</script>
```

위와 같이 각 프래그먼트는 정점을 기준으로 상대적인 위치에 따라 선형 보간된 색상값을 갖게 됩니다.

색상을 적용해서 그리기
------------

이제 셰이더 프로그램에서 색상 변수를 초기화하고 활성화하는 내용을 `initShaders()`에 추가합니다:

```js
vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(vertexColorAttribute);
```

다음은 정사각형을 그릴 때 색상 정보를 사용하도록 `drawScene()`을 수정합니다:

```js
gl.bindBuffer(gl.ARRAY\_BUFFER, squareVerticesColorBuffer);
gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
```

이제 WebGL이 호환되는 브라우저에서 샘플을 보면, 검정색 바탕에 아래와 같은 정사각형이 그려지는 것을 볼 수 있습니다:

전체 코드 보기 | 새창에서 데모 실행하기

* « 이전
* 다음 »


코드에서의 텍스쳐 - Web API

코드에서의 텍스쳐
=========

* « 이전
* 다음 » (en-US)

단편화된 쉐이더들과 함께 순차적으로 보여주는 간단한 데모

### 코드로 텍스쳐 그리기

fragment shader에서 픽셀 단위로 계산된 스프라이트 텍스쳐를 그립니다.
```

<p>Texture from code. Simple demonstration of procedural texturing</p>
```

```
<canvas>Your browser does not seem to support HTML5 canvas.</canvas>
```

```
body {
  text-align: center;
}
canvas {
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```html
<script type="x-shader/x-vertex" id="vertex-shader">
 #version 100
 precision highp float;

 attribute vec2 position;

 void main() {
 gl\_Position = vec4(position, 0.0, 1.0);
 gl\_PointSize = 128.0;
 }
</script>
```

```html
<script type="x-shader/x-fragment" id="fragment-shader">
 #version 100
 precision mediump float;
 void main() {
 vec2 fragmentPosition = 2.0\*gl\_PointCoord - 1.0;
 float distance = length(fragmentPosition);
 float distanceSqrd = distance \* distance;
 gl\_FragColor = vec4(
 0.2/distanceSqrd,
 0.1/distanceSqrd,
 0.0, 1.0 );
 }
</script>
```

```
;(function(){
```

```js
"use strict";
window.addEventListener("load", setupWebGL, false);
var gl, program;
function setupWebGL(evt) {
  window.removeEventListener(evt.type, setupWebGL, false);
  if (!(gl = getRenderingContext())) return;

  var source = document.querySelector("#vertex-shader").innerHTML;
  var vertexShader = gl.createShader(gl.VERTEX\_SHADER);
  gl.shaderSource(vertexShader, source);
  gl.compileShader(vertexShader);
  source = document.querySelector("#fragment-shader").innerHTML;
  var fragmentShader = gl.createShader(gl.FRAGMENT\_SHADER);
  gl.shaderSource(fragmentShader, source);
  gl.compileShader(fragmentShader);
  program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.detachShader(program, vertexShader);
  gl.detachShader(program, fragmentShader);
  gl.deleteShader(vertexShader);
  gl.deleteShader(fragmentShader);
  if (!gl.getProgramParameter(program, gl.LINK\_STATUS)) {
    var linkErrLog = gl.getProgramInfoLog(program);
    cleanup();
    document.querySelector("p").innerHTML =
      "Shader program did not link successfully. " + "Error log: " + linkErrLog;
    return;
  }
  initializeAttributes();
  gl.useProgram(program);
  gl.drawArrays(gl.POINTS, 0, 1);
  cleanup();
}

var buffer;
function initializeAttributes() {
  gl.enableVertexAttribArray(0);
  buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffer);
  gl.bufferData(gl.ARRAY\_BUFFER, new Float32Array([0.0, 0.0]), gl.STATIC\_DRAW);
  gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
}

function cleanup() {
  gl.useProgram(null);
  if (buffer) gl.deleteBuffer(buffer);
  if (program) gl.deleteProgram(program);
}
```

```
function getRenderingContext() {
  var canvas = document.querySelector("canvas");
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  var gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  if (!gl) {
    var paragraph = document.querySelector("p");
    paragraph.innerHTML =
      "Failed to get WebGL context." +
      "Your browser or device may not support WebGL.";
    return null;
  }
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR\_BUFFER\_BIT);
  return gl;
}
```

```
})();
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전
* 다음 » (en-US)


WebGL 찾기 - Web API

WebGL 찾기
========

* « 이전 (en-US)
* 다음 » (en-US)

이 예시는 어떻게 WebGL 랜더링 컨텐츠를 찾고, 유저에게 결과를 보고할지를 보여줍니다.

### WebGL 기능 검색

이 첫 예시에서, 우리는 브라우저가 WebGL를 지원하는지 아닌 지를 확인합니다. 우리는 `canvas` element로 부터 WebGL rendering context (en-US)을 얻기 위하여 노력합니다. WebGL rendering context (en-US)는 당신이 설정하고 그래픽 기계의 상태를 쿼리하고, WebGl에 데이터를 전송하고, 그리기 명령어들을 실행할 수 있는 인터페이스입니다.

한 문장 인터페이스에서 그래픽 기계의 상태를 저장하는 것은 WebGL에 고유하지 않습니다. 이것은 또한 다른 그래픽 {̣{Glossary("API")}}, canvas 2D rendering context (en-US)에 의해 행해집니다. 하지만 특성과 당신이 만드는 변수들은 각 {̣{Glossary("API")}}에서 다를 수 있습니다.

```html
<p>[ Here would go the result of WebGL feature detection ]</p>
<button>Press here to detect WebGLRenderingContext</button>
```

```css
body {
  text-align: center;
}
button {
  display: block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```js
// Run everything inside window load event handler, to make sure
// DOM is fully loaded and styled before trying to manipulate it.
window.addEventListener(
  "load",
  function () {
    var paragraph = document.querySelector("p"),
      button = document.querySelector("button");
    // Adding click event handler to button.
    button.addEventListener("click", detectWebGLContext, false);
    function detectWebGLContext() {
      // Create canvas element. The canvas is not added to the
      // document itself, so it is never displayed in the
      // browser window.
      var canvas = document.createElement("canvas");
      // Get WebGLRenderingContext from canvas element.
      var gl =
        canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
      // Report the result.
      if (gl && gl instanceof WebGLRenderingContext) {
        paragraph.innerHTML = "Congratulations! Your browser supports WebGL.";
      } else {
        paragraph.innerHTML =
          "Failed to get WebGL context. " +
          "Your browser or device may not support WebGL.";
      }
    }
  },
  false,
);
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전 (en-US)
* 다음 » (en-US)


Hello vertex attributes - Web API

Hello vertex attributes
=======================

* « 이전
* 다음 »

쉐이더 프로그래밍과 유저 인터페이스를 정점 속성을 이용하여 합치기.

### Hello World program in GLSL

어떻게 GPU 메모리에 데이터를 저장함으로써 쉐이더 프로그램에 입력 값을 넣을 수 있을까?
```

<p>
  First encounter with attributes and sending data to GPU. Click on the canvas
  to change the horizontal position of the square.
</p>
```

```
<canvas>Your browser does not seem to support HTML5 canvas.</canvas>
```

```
body {
  text-align: center;
}
canvas {
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```html
<script type="x-shader/x-vertex" id="vertex-shader">
 #version 100
 precision highp float;

 attribute float position;

 void main() {
 gl\_Position = vec4(position, 0.0, 0.0, 1.0);
 gl\_PointSize = 64.0;
 }
</script>
```

```html
<script type="x-shader/x-fragment" id="fragment-shader">
 #version 100
 precision mediump float;
 void main() {
 gl\_FragColor = vec4(0.18, 0.54, 0.34, 1.0);
 }
</script>
```

```
;(function(){
```

```js
"use strict"
window.addEventListener("load", setupWebGL, false);
var gl,
  program;
function setupWebGL (evt) {
  window.removeEventListener(evt.type, setupWebGL, false);
  if (!(gl = getRenderingContext()))
    return;

  var source = document.querySelector("#vertex-shader").innerHTML;
  var vertexShader = gl.createShader(gl.VERTEX\_SHADER);
  gl.shaderSource(vertexShader,source);
  gl.compileShader(vertexShader);
  source = document.querySelector("#fragment-shader").innerHTML
  var fragmentShader = gl.createShader(gl.FRAGMENT\_SHADER);
  gl.shaderSource(fragmentShader,source);
  gl.compileShader(fragmentShader);
  program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.detachShader(program, vertexShader);
  gl.detachShader(program, fragmentShader);
  gl.deleteShader(vertexShader);
  gl.deleteShader(fragmentShader);
  if (!gl.getProgramParameter(program, gl.LINK\_STATUS)) {
    var linkErrLog = gl.getProgramInfoLog(program);
    cleanup();
    document.querySelector("p").innerHTML =
      "Shader program did not link successfully. "
      + "Error log: " + linkErrLog;
    return;
  }

  initializeAttributes();
  gl.useProgram(program);
  gl.drawArrays(gl.POINTS, 0, 1);

  document.querySelector("canvas").addEventListener("click",
    function (evt) {
      var clickXrelativToCanvas =
          evt.pageX - evt.target.offsetLeft;
      var clickXinWebGLCoords =
          2.0 \* (clickXrelativToCanvas- gl.drawingBufferWidth/2)
          / gl.drawingBufferWidth;
      gl.bufferData(gl.ARRAY\_BUFFER,
        new Float32Array([clickXinWebGLCoords]), gl.STATIC\_DRAW);
      gl.drawArrays(gl.POINTS, 0, 1);
    }, false);
}

var buffer;
function initializeAttributes() {
  gl.enableVertexAttribArray(0);
  buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffer);
  gl.bufferData(gl.ARRAY\_BUFFER, new Float32Array([0.0]), gl.STATIC\_DRAW);
  gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
}

window.addEventListener("beforeunload", cleanup, true);
function cleanup() {
  gl.useProgram(null);
  if (buffer)
    gl.deleteBuffer(buffer);
  if (program)
    gl.deleteProgram(program);
}
```

```
function getRenderingContext() {
  var canvas = document.querySelector("canvas");
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  var gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  if (!gl) {
    var paragraph = document.querySelector("p");
    paragraph.innerHTML =
      "Failed to get WebGL context." +
      "Your browser or device may not support WebGL.";
    return null;
  }
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR\_BUFFER\_BIT);
  return gl;
}
```

```
})();
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전
* 다음 »


애니메이션 잘라내기 - Web API

애니메이션 잘라내기
==========

* « 이전 (en-US)
* 다음 » (en-US)

활동을 청소하고 잘라내는 어떤 애니메이션 장난

### 애니메이션 잘라내기

이번 예시에서는, 우리는 사각형을 `scissor()` (en-US) 와`clear()` (en-US) 을 이용하여 그려볼 것입니다. 우리는 다시 애니메이션 루프를 타이머를 이용하여 구축할 것입니다. 이번에는 매 프레임(우리는 프레임 비율을 대강 매 17ms 마다 설정했습니다.) 대마다 업데이트 되는 사각형(잘라내는 영역)의 경우임을 주목하세요.

반대로, 사각형의 색 (`clearColor` (en-US)으로 설정되는)은 오직 새로운 사각형이 생성될 때만 업데이트 됩니다. 이것은 상태 머신으로써 WebGL 을 보여줄 좋은 데모입니다. 각 사각형에 대하여 우리는 그것의 색을 결정하고, 매 프레임마다 위치를 결정합니다. WebGl의 깨끗한 색 상태는 새로운 사각형이 생성되어 우리가 그것을 다시 바꿀 때까지 설정 값으로 남아있습니다.
```

<p>
  WebGL animation by clearing the drawing buffer with solid color and applying
  scissor test.
</p>
<button id="animation-onoff">
  Press here to
  <strong>[verb goes here]</strong>
  the animation
</button>
```

```
<canvas>Your browser does not seem to support HTML5 canvas.</canvas>
```

```
body {
  text-align: center;
}
canvas {
  display: block;
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```
;(function(){
```

```js
"use strict"
window.addEventListener("load", setupAnimation, false);
// Variables to hold the WebGL context, and the color and
// position of animated squares.
var gl,
  color = getRandomColor(),
  position;

function setupAnimation (evt) {
  window.removeEventListener(evt.type, setupAnimation, false);
  if (!(gl = getRenderingContext()))
    return;

  gl.enable(gl.SCISSOR\_TEST);
  gl.clearColor(color[0], color[1], color[2], 1.0);
  // Unlike the browser window, vertical position in WebGL is
  // measured from bottom to top. In here we set the initial
  // position of the square to be at the top left corner of the
  // drawing buffer.
  position = [0, gl.drawingBufferHeight];

  var button = document.querySelector("button");
  var timer;
  function startAnimation(evt) {
    button.removeEventListener(evt.type, startAnimation, false);
    button.addEventListener("click", stopAnimation, false);
    document.querySelector("strong").innerHTML = "stop";
    timer = setInterval(drawAnimation, 17);
    drawAnimation();
  }
  function stopAnimation(evt) {
    button.removeEventListener(evt.type, stopAnimation, false);
    button.addEventListener("click", startAnimation, false);
    document.querySelector("strong").innerHTML = "start";
    clearInterval(timer);
  }
  stopAnimation({type: "click"});
}

// Variables to hold the size and velocity of the square.
var size = [60, 60],
  velocity = 3.0;
function drawAnimation () {
  gl.scissor(position[0], position[1], size[0] , size[1]);
  gl.clear(gl.COLOR\_BUFFER\_BIT);
  // Every frame the vertical position of the square is
  // decreased, to create the illusion of movement.
  position[1] -= velocity;
  // When the sqaure hits the bottom of the drawing buffer,
  // we override it with new square of different color and
  // velocity.
  if (position[1] < 0) {
    // Horizontal position chosen randomly, and vertical
    // position at the top of the drawing buffer.
    position = [
      Math.random()\*(gl.drawingBufferWidth - size[0]),
      gl.drawingBufferHeight
    ];
    // Random velocity between 1.0 and 7.0
    velocity = 1.0 + 6.0\*Math.random();
    color = getRandomColor();
    gl.clearColor(color[0], color[1], color[2], 1.0);
  }
}

function getRandomColor() {
  return [Math.random(), Math.random(), Math.random()];
}
```

```
function getRenderingContext() {
  var canvas = document.querySelector("canvas");
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  var gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  if (!gl) {
    var paragraph = document.querySelector("p");
    paragraph.innerHTML =
      "Failed to get WebGL context." +
      "Your browser or device may not support WebGL.";
    return null;
  }
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR\_BUFFER\_BIT);
  return gl;
}
```

```
})();
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전 (en-US)
* 다음 » (en-US)


Hello GLSL - Web API

Hello GLSL
==========

* « 이전 (en-US)
* 다음 »

매우 간단한 색이 있는 단단한 사각형을 그려주는 쉐이더 프로그램

**참고:** 이 예시는 대부분 모든 최신의 데스크탑 브라우저에서 동작합니다. 하지만 어떤 모바일 혹은 낡은 브라우저에서는 동작하지 않습니다. 만약 캔버스가 공백인 상태로 남아있다면, 당신은 정확히 똑같은 것을 그리는 다음 예시의 결과를 확인하실 수 있습니다. 하지만 다음으로 넘어가기 전에, 여기서 설명과 코드를 읽고 가는 것을 기억하세요

### Hello World 프로그램 in GLSL

매우 간단한 첫 쉐이더 프로그램
```

<p>Hello World! Hello GLSL!</p>
```

```
<canvas>Your browser does not seem to support HTML canvas.</canvas>
```

```
body {
  text-align: center;
}
canvas {
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```html
<script type="x-shader/x-vertex" id="vertex-shader">
 #version 100
 void main() {
 gl\_Position = vec4(0.0, 0.0, 0.0, 1.0);
 gl\_PointSize = 64.0;
 }
</script>
```

```html
<script type="x-shader/x-fragment" id="fragment-shader">
 #version 100
 void main() {
 gl\_FragColor = vec4(0.18, 0.54, 0.34, 1.0);
 }
</script>
```

```
;(() => {
  "use strict";
```

```js
window.addEventListener("load", setupWebGL, false);
let gl;
let program;

function setupWebGL(evt) {
  window.removeEventListener(evt.type, setupWebGL, false);
  if (!(gl = getRenderingContext())) return;

  let source = document.querySelector("#vertex-shader").innerHTML;
  const vertexShader = gl.createShader(gl.VERTEX\_SHADER);
  gl.shaderSource(vertexShader, source);
  gl.compileShader(vertexShader);

  source = document.querySelector("#fragment-shader").innerHTML;
  const fragmentShader = gl.createShader(gl.FRAGMENT\_SHADER);
  gl.shaderSource(fragmentShader, source);
  gl.compileShader(fragmentShader);
  program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.detachShader(program, vertexShader);
  gl.detachShader(program, fragmentShader);
  gl.deleteShader(vertexShader);
  gl.deleteShader(fragmentShader);
  if (!gl.getProgramParameter(program, gl.LINK\_STATUS)) {
    const linkErrLog = gl.getProgramInfoLog(program);
    cleanup();
    document.querySelector("p").textContent =
      `Shader program did not link successfully. Error log: ${linkErrLog}`;
    return;
  }

  initializeAttributes();

  gl.useProgram(program);
  gl.drawArrays(gl.POINTS, 0, 1);

  cleanup();
}

let buffer;
function initializeAttributes() {
  gl.enableVertexAttribArray(0);
  buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY\_BUFFER, buffer);
  gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
}

function cleanup() {
  gl.useProgram(null);
  if (buffer) {
    gl.deleteBuffer(buffer);
  }
  if (program) {
    gl.deleteProgram(program);
  }
}
```

```
function getRenderingContext() {
  const canvas = document.querySelector("canvas");
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  const gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  if (!gl) {
    const paragraph = document.querySelector("p");
    paragraph.textContent =
      "Failed. Your browser or device may not support WebGL.";
    return null;
  }
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR\_BUFFER\_BIT);
  return gl;
}
```

```
})();
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전 (en-US)
* 다음 »


클릭을 통한 청소 - Web API

클릭을 통한 청소
=========

* « 이전 (en-US)
* 다음 »

어떻게 유저와 그래픽 기능들과의 상호작용을 결합시킬까. 유저가 클릭을 할 때 랜덤의 색을 가진 랜더링 컨텍스트를 청소하는 것.

### 랜덤 색을 가진 랜더링 컨텍스트를 청소

이 간단한 예시는 어떻게 유저 인터페이스와 WebGL를 결합할지에 대한 설명을 제공합니다. 유저가 캔버스 혹은 버튼을 클릭할 때 마다, 캔버스는 랜덤으로 선택된 색으로 초기화됩니다.

어떻게 내장된 WebGL 기능이 이벤트 핸들러의 내부를 부르는 지를 주목하세요.

```html
<p>
  A very simple WebGL program that still shows some color and user interaction.
</p>
<p>
  You can repeatedly click the empty canvas or the button below to change color.
</p>
<canvas id="canvas-view"
 >Your browser does not seem to support HTML5 canvas.</canvas
>
<button id="color-switcher">Press here to switch color</button>
```

```css
body {
  text-align: center;
}
canvas {
  display: block;
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: inline-block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```js
window.addEventListener(
  "load",
  function setupWebGL(evt) {
    "use strict";

    // Cleaning after ourselves. The event handler removes
    // itself, because it only needs to run once.
    window.removeEventListener(evt.type, setupWebGL, false);

    // Adding the same click event handler to both canvas and
    // button.
    var canvas = document.querySelector("#canvas-view");
    var button = document.querySelector("#color-switcher");
    canvas.addEventListener("click", switchColor, false);
    button.addEventListener("click", switchColor, false);

    // A variable to hold the WebGLRenderingContext.
    var gl;

    // The click event handler.
    function switchColor() {
      // Referring to the externally defined gl variable.
      // If undefined, try to obtain the WebGLRenderingContext.
      // If failed, alert user of failure.
      // Otherwise, initialize the drawing buffer (the viewport).
      if (!gl) {
        gl =
          canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
        if (!gl) {
          alert(
            "Failed to get WebGL context.\n" +
              "Your browser or device may not support WebGL.",
          );
          return;
        }
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
      }
      // Get a random color value using a helper function.
      var color = getRandomColor();
      // Set the clear color to the random color.
      gl.clearColor(color[0], color[1], color[2], 1.0);
      // Clear the context with the newly set color. This is
      // the function call that actually does the drawing.
      gl.clear(gl.COLOR\_BUFFER\_BIT);
    }

    // Random color helper function.
    function getRandomColor() {
      return [Math.random(), Math.random(), Math.random()];
    }
  },
  false,
);
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전 (en-US)
* 다음 »


간단한 색깔 애니메이션 - Web API

간단한 색깔 애니메이션
============

* « 이전
* 다음 » (en-US)

WebGL 를 청소함으로써 매 초 마다 랜덤 색이 버퍼에 그려지는 매우 기본적인 색깔 애니메이션.

### 초기화가 있는 컬러 애니메이션

이 예시는 유저 인터페이스 뿐만 아니라, WebGL와 함께 간단한 컬러 애니메이션 설명을 제공합니다. 유저는 버튼을 클릭함으로써, 애니메이션을 시작하고, 종료하고, 다시시작할 수 있습니다.

이번에 우리는 이벤트 핸들러 내부에서 WebGL 함수 콜을 넣을 것입니다. 클릭 이벤트 핸들러는 추가적으로 시작하고 멈추는 기본적인 유저 상호작용을 가능하게 합니다. 타이머와 타이머 핸들러 기능은 애니메이션 반복을 구축합니다. 애니메이션 반복은 일정한 주기로 실행되는 그리기 명령의 집합입니다.(일반적으로 모든 프레임; 이 경우 초당 1번)

```html
<p>A simple WebGL program that shows color animation.</p>
<p>You can click the button below to toggle the color animation on or off.</p>
<canvas id="canvas-view"
 >Your browser does not seem to support HTML5 canvas.</canvas
>
<button id="animation-onoff">
  Press here to
  <strong>[verb goes here]</strong>
  the animation
</button>
```

```css
body {
  text-align: center;
}
canvas {
  display: block;
  width: 280px;
  height: 210px;
  margin: auto;
  padding: 0;
  border: none;
  background-color: black;
}
button {
  display: inline-block;
  font-size: inherit;
  margin: auto;
  padding: 0.6em;
}
```

```js
window.addEventListener(
  "load",
  function setupAnimation(evt) {
    "use strict";
    window.removeEventListener(evt.type, setupAnimation, false);

    // A variable to hold a timer that drives the animation.
    var timer;

    // Click event handlers.
    var button = document.querySelector("#animation-onoff");
    var verb = document.querySelector("strong");
    function startAnimation(evt) {
      button.removeEventListener(evt.type, startAnimation, false);
      button.addEventListener("click", stopAnimation, false);
      verb.innerHTML = "stop";
      // Setup animation loop by redrawing every second.
      timer = setInterval(drawAnimation, 1000);
      // Give immediate feedback to user after clicking, by
      // drawing one animation frame.
      drawAnimation();
    }
    function stopAnimation(evt) {
      button.removeEventListener(evt.type, stopAnimation, false);
      button.addEventListener("click", startAnimation, false);
      verb.innerHTML = "start";
      // Stop animation by clearing the timer.
      clearInterval(timer);
    }
    // Call stopAnimation() once to setup the initial event
    // handlers for canvas and button.
    stopAnimation({ type: "click" });

    var gl;
    function drawAnimation() {
      if (!gl) {
        var canvas = document.getElementById("canvas-view");
        gl =
          canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
        if (!gl) {
          clearInterval(timer);
          alert(
            "Failed to get WebGL context.\n" +
              "Your browser or device may not support WebGL.",
          );
          return;
        }
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
      }

      // Get a random color value using a helper function.
      var color = getRandomColor();
      // Set the WebGLRenderingContext clear color to the
      // random color.
      gl.clearColor(color[0], color[1], color[2], 1.0);
      // Clear the context with the newly set color.
      gl.clear(gl.COLOR\_BUFFER\_BIT);
    }

    // Random color helper function.
    function getRandomColor() {
      return [Math.random(), Math.random(), Math.random()];
    }
  },
  false,
);
```

이 예시 코드는 GitHub에서도 확인 가능합니다.

* « 이전
* 다음 » (en-US)


웹 어플리케이션에서 파일 사용하기 - Web API

웹 어플리케이션에서 파일 사용하기
==================

HTML5의 DOM으로 추가된 File API를 사용하여, 이제 웹 컨텐츠가 사용자에게 로컬 파일을 선택한 후 파일의 컨텐츠를 읽도록 요청할 수 있습니다. 이 선택은 HTML `<input>` (en-US) 엘리먼트나 드래그 앤 드랍을 사용하는 것으로도 수행할 수 있습니다.

원하신다면 확장 기능이나 다른 브라우저 크롬 코드에서도 DOM File API를 사용하실 수 있습니다. 하지만, 조심해야할 몇 가지 부가적인 기능들이 있음을 유의하세요. 자세한 내용은 크롬 코드에서 DOM File API 사용하기 글을 확인하세요.

선택한 파일에 접근하기
------------

다음 HTML을 생각해봅시다.

```html
<input type="file" id="input" />
```

File API는 사용자에 의해 선택된 파일을 나타내는 객체인 `File`을 포함하는 `FileList`에 접근할 수 있게 해줍니다.

사용자가 하나의 파일만을 선택한 경우, 리스트의 첫 번째 파일만 고려하면 됩니다.

기존의 DOM 셀렉터를 사용하여 선택된 하나의 파일에 접근하기:

```js
const selectedFile = document.getElementById("input").files[0];
```

### change 이벤트에서 선택한 파일에 접근하기

`change` 이벤트를 통해 `FileList`에 접근할수도 있습니다(필수는 아닙니다).

```html
<input type="file" id="input" onchange="handleFiles(this.files)" />
```

사용자가 하나의 파일을 선택할 때, 사용자에 의해 선택된 파일을 나타내는 객체인 `File`을 포함하는 `FileList`와 함께 `handlerFiles()` 함수가 호출됩니다.

사용자가 여러 파일을 선택할 수 있도록 하길 원할 경우, 간단히 `input` 엘리먼트에서 `multiple` 속성을 사용하면됩니다.

```html
<input type="file" id="input" multiple onchange="handleFiles(this.files)" />
```

이 경우, `handleFiles()` 함수로 전달된 파일 리스트는 사용자가 선택한 각 파일에 대해 하나의 `File` 객체를 갖습니다.

### 동적으로 change 리스너 추가하기

`change` 이벤트 리스너를 추가하려면 `EventTarget.addEventListener()`를 다음과 같이 사용해야합니다.

```js
const inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {
  const fileList = this.files; /\* 이제 파일 리스트로 작업할 수 있습니다 \*/
}
```

이 경우에는, 파라미터를 전달한 이벤트 핸들러에의해 호출된 이전 예제에서와 달리, `handleFiles()` 함수 자체가 이벤트 핸들러임을 유의하세요.

선택한 파일에 대한 정보 얻기
----------------

DOM에 의해 제공된 `FileList` 객체는 사용자에 의해 선택된 모든 파일을 각각 `File` 객체로 지정하여 나열합니다. 파일 리스트의 `length` 속성의 값을 확인하여 사용자가 선택한 파일의 수를 결정할 수 있습니다.

```js
const numFiles = files.length;
```

개별 `File` 객체는 리스트를 간단히 배열처럼 접근하여 얻을 수 있습니다.

```js
for (let i = 0, numFiles = files.length; i < numFiles; i++) {
  const file = files[i];
  ..
}
```

위 반복문은 파일 리스트의 모든 파일을 순회합니다.

파일에 대한 유용한 정보를 포함하는 `File` 객체는 세 가지 속성을 제공합니다.

`name`

읽기 전용 문자열인 파일의 이름입니다. 단순한 파일 이름이며, 경로에 대한 정보는 포함하지 않습니다.

`size`

읽기 전용 64비트 정수의 바이트 단위 파일의 크기입니다.

`type`

읽기 전용 문자열인 파일의 MIME 타입입니다. 결정할 수 없는 타입인 경우 `""`입니다.

### 예시: 파일 크기 보기

다음 예시는 `size` 프로퍼티를 사용하는 방법을 보여줍니다.

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>File(s) size</title>
    <script>
 function updateSize() {
 let nBytes = 0,
 oFiles = document.getElementById("uploadInput").files,
 nFiles = oFiles.length;
 for (let nFileId = 0; nFileId < nFiles; nFileId++) {
 nBytes += oFiles[nFileId].size;
 }
 let sOutput = nBytes + " bytes";
 // multiples approximation을 위한 선택적 코드
 for (
 let aMultiples = [
 "KiB",
 "MiB",
 "GiB",
 "TiB",
 "PiB",
 "EiB",
 "ZiB",
 "YiB",
 ],
 nMultiple = 0,
 nApprox = nBytes / 1024;
 nApprox > 1;
 nApprox /= 1024, nMultiple++
 ) {
 sOutput =
 nApprox.toFixed(3) +
 " " +
 aMultiples[nMultiple] +
 " (" +
 nBytes +
 " bytes)";
 }
 // 선택적 코드의 끝
 document.getElementById("fileNum").innerHTML = nFiles;
 document.getElementById("fileSize").innerHTML = sOutput;
 }
 </script>
  </head>

  <body onload="updateSize();">
    <form name="uploadForm">
      <p>
        <input
 id="uploadInput"
 type="file"
 name="myFiles"
 onchange="updateSize();"
 multiple />
        selected files: <span id="fileNum">0</span>; total size:
        <span id="fileSize">0</span>
      </p>
      <p><input type="submit" value="Send file" /></p>
    </form>
  </body>
</html>
```

click() 메소드를 사용하여 숨겨진 파일 input 엘리먼트 사용하기
----------------------------------------

세련되지 않은 파일 `<input>` (en-US) 엘리먼트를 숨기고 파일 선택기를 열고 사용자에 의해 선택된 파일 또는 파일들을 보여주는 여러분만의 인터페이스를 제공할 수 있습니다. input 엘리먼트를 `display:none` 으로 스타일링하고 `<input>` (en-US) 엘리먼트에 `click()` 메소드를 호출하는 것으로 이를 수행할 수 있습니다.

다음 HTML을 생각해봅시다.

```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 style="display:none"
 onchange="handleFiles(this.files)" />
<button id="fileSelect">Select some files</button>
```

`click` 이벤트를 다루는 코드는 다음과 같을 것입니다.

```js
const fileSelect = document.getElementById("fileSelect"),
  fileElem = document.getElementById("fileElem");

fileSelect.addEventListener(
  "click",
  function (e) {
    if (fileElem) {
      fileElem.click();
    }
  },
  false,
);
```

여러분이 원하는 파일 선택기를 열기위한 새로운 버튼을 스타일링할 수 있습니다.

label 엘리먼트를 사용하여 숨겨진 파일 input 엘리먼트 실행하기
---------------------------------------

JavaScript(click() 메소드)를 사용하지 않고 파일 선택기를 열도록 허용하기 위해 `<label>` 엘리먼트가 사용될 수 있습니다. 이 경우에는 input 엘리먼트가 반드시 `display: none`(또는 `visibility: hidden`)을 사용하여 숨긴상태가 아니어야하며, 그렇지 않을 경우 라벨은 키보드로 접근이 불가하다는 것을 유의하세요. 대신 외관상으로 숨기기 테크닉을 사용하세요.

다음 HTML과

```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 class="visually-hidden" />
<label for="fileElem">Select some files</label>
```

CSS를 생각해봅시다.

```css
.visually-hidden {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
}

input.visually-hidden:focus + label {
  outline: thin dotted;
}
```

`fileElem.click()`을 호출하기위해 JavaScript 코드를 추가할 필요가 없습니다. 또한 이 경우에는 여러분이 원하는대로 label 엘리먼트를 스타일링 할 수 있습니다. 여러분은 숨겨진 input 필드의 포커싱 상태를 시각적인 신호(위에서 보여진 outline이나, background-color 또는 box-shadow)로 label에 제공해야합니다. (이 글의 작성 시점에서, Firefox는 `<input type="file">` 엘리먼트에 대한 시각적 신호를 보여주지 않습니다.)

드래그 앤 드랍을 사용하여 파일 선택하기
----------------------

사용자가 파일을 웹 어플리케이션으로 드래그 앤 드랍하도록 할 수도 있습니다.

첫 단계는 드랍 영역을 설정하는 것입니다. 드랍을 허용할 컨텐츠의 정확한 영역은 어플리케이션의 디자인에따라 아주 달라질 수 있지만, 드랍 이벤트를 받는 엘리먼트를 만드는 것은 간단합니다.

```js
let dropbox;

dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
```

이 예시에서는, `dropbox`라는 ID를 갖는 엘리먼트를 드랍 영역으로 변경합니다. `dragenter` (en-US), `dragover` (en-US), `drop` (en-US) 이벤트를 위한 리스너를 추가하는 것으로 이를 수행할 수 있습니다.

우리의 경우에는, `dragenter`와 `dragover` 이벤트로 무언가를 진짜 할 필요는 없으므로, 두 함수는 모두 단순합니다. 두 함수는 단지 이벤트의 전파를 중단하고 기본 동작이 발생하는 것을 방지합니다.

```js
function dragenter(e) {
  e.stopPropagation();
  e.preventDefault();
}

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
}
```

진짜 마법은 `drop()` 함수에서 발생합니다.

```js
function drop(e) {
  e.stopPropagation();
  e.preventDefault();

  const dt = e.dataTransfer;
  const files = dt.files;

  handleFiles(files);
}
```

여기에서, 우리는 이벤트로부터 `dataTransfer` 필드를 추출하고, 그로부터 파일 리스트를 가져온 후, `handleFiles()`로 전달합니다. 이 지점부터, 파일을 다루는 것은 사용자가 `input` 엘리먼트를 사용했든 드래그 앤 드랍을 사용했든 동일합니다.

예시: 사용자가 선택한 이미지의 섬네일 보여주기
--------------------------

여러분이 차세대 사진 공유 웹사이트를 개발중이며 HTML5를 사용하여 사진이 실제로 업로드되기 전에 이미지의 섬네일 미리보기를 표시하길 원한다고 가정해봅시다. 여러분은 앞서 설명한대로 input 엘리먼트나 드랍 영역을 설정하고 아래와 같은 `handleFiles()` 함수를 호출하면됩니다.

```js
function handleFiles(files) {
  for (let i = 0; i < files.length; i++) {
    const file = files[i];

    if (!file.type.startsWith("image/")) {
      continue;
    }

    const img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img); // "preview"가 결과를 보여줄 div 출력이라 가정.

    const reader = new FileReader();
    reader.onload = (function (aImg) {
      return function (e) {
        aImg.src = e.target.result;
      };
    })(img);
    reader.readAsDataURL(file);
  }
}
```

H여기에서 사용자가 선택한 파일을 다루는 반복문은 각 파일의 `type` 속성을 보고 MIME 타입이 "`image/`" 문자열로 시작하는지를 확인합니다. 이미지인 각 파일에 대해서는, 새로운 `img` 엘리먼트를 생성합니다. CSS를 사용하여 보기 좋은 테두리나 그림자를 설정할 수 있고 이미지의 크기를 지정할 수 있으므로, 스타일링에 대해서는 여기에서 다룰 필요는 없습니다.

각 이미지는 각각에 추가된 CSS 클래스 `obj`를 가져, DOM 트리에서의 탐색을 더 쉽게만듭니다. 각 이미지에 이미지에 대한 `File`을 지정하는 `file` 속성도 추가합니다(이는 나중에 실제로 업로드를 위한 이미지를 fetch 할 수 있게해줍니다). `Node.appendChild()`를 사용하여 다큐먼트의 미리보기 영역에 새로운 섬네일을 추가합니다.

다음으로, `FileReader`를 설정하여 이미지 로딩과 이를 `img` 엘리먼트에 추가하는 것을 비동기적으로 처리합니다. 새로운 `FileReader` 객체를 생성한 후에, `onload` 함수를 설정하고 `readAsDataURL()`을 호출하여 백그라운드에서 읽기 작업을 시작합니다. 이미지 파일의 전체 컨텐츠가 로드되었을 때, onload 콜백으로 전달되는 `data:` URL로 변환됩니다. 이 루틴을 구현하면 img 엘리먼트의 src 속성을 로드된 이미지로 설정하여 사용자 화면의 섬네일에 이미지가 나타나납니다.

객체 URL 사용하기
-----------

DOM `window.URL.createObjectURL()` (en-US) 및 `window.URL.revokeObjectURL()` (en-US) 메소드에 대한 지원을 소개했습니다. 이 메소드들은 사용자의 컴퓨터에 있는 로컬 파일을 포함해, DOM `File` 객체를 사용해 참조된 데이터에 대한 참조로 사용할 수 있는 간단한 URL 문자열을 생성할 수 있게 해줍니다.

HTML에 URL로 참조하길 원하는 `File` 객체가 있다면, 다음과 같이 객체 URL을 생성할 수 있습니다.

```js
const objectURL = window.URL.createObjectURL(fileObj);
```

객체 URL은 `File` 객체를 식별하는 문자열입니다. `window.URL.createObjectURL()` (en-US)을 호출할때마다, 여러분이 이미 해당 파일에 대한 객체 URL을 생성했더라도 고유한 객체 URL이 생성됩니다. 각각은 반드시 해제되어야 합니다. 객체 URL은 다큐먼트가 unload될 때 자동으로 해제되지만, 여러분의 페이지가 동적으로 이를 사용할 경우 `window.URL.revokeObjectURL()` (en-US)을 호출하여 명시적으로 해제해야합니다.

```js
window.URL.revokeObjectURL(objectURL);
```

예시: 객체 URL을 사용하여 이미지 표시하기
-------------------------

다음 예시는 객체 URL을 사용하여 이미지 섬네일을 표시합니다. 부가적으로, 파일의 이름과 크기를 포함한 다른 정보도 표시합니다.

인터페이스를 나타내는 HTML은 다음과 같습니다.

```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 style="display:none"
 onchange="handleFiles(this.files)" />
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
  <p>No files selected!</p>
</div>
```

위 코드는 파일 선택기를 불러오는 링크와 우리의 파일 `<input>` (en-US) 엘리먼트를 설정합니다(파일 input을 숨겨 덜 매력적인 사용자 인터페이스가 표시되는 것을 방지하였으므로). 이는 파일 선택기를 불러오는 메소드와 마찬가지로, Using hidden file input elements using the click() method 섹션에서 설명합니다.

`handleFiles()` 메소드는 다음과 같습니다.

```js
window.URL = window.URL || window.webkitURL;

const fileSelect = document.getElementById("fileSelect"),
  fileElem = document.getElementById("fileElem"),
  fileList = document.getElementById("fileList");

fileSelect.addEventListener(
  "click",
  function (e) {
    if (fileElem) {
      fileElem.click();
    }
    e.preventDefault(); // "#" 해시로 이동을 방지
  },
  false,
);

function handleFiles(files) {
  if (!files.length) {
    fileList.innerHTML = "<p>No files selected!</p>";
  } else {
    fileList.innerHTML = "";
    const list = document.createElement("ul");
    fileList.appendChild(list);
    for (let i = 0; i < files.length; i++) {
      const li = document.createElement("li");
      list.appendChild(li);

      const img = document.createElement("img");
      img.src = window.URL.createObjectURL(files[i]);
      img.height = 60;
      img.onload = function () {
        window.URL.revokeObjectURL(this.src);
      };
      li.appendChild(img);
      const info = document.createElement("span");
      info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
      li.appendChild(info);
    }
  }
}
```

`fileList` ID로 `<div>` (en-US)의 URL을 페칭하는 것으로 시작합니다. 이는 섬네일을 포함하여 파일 리스트로 삽입하는 블록입니다.

`handleFiles()`로 전달된 `FileList` 객체가 `null`인 경우, 블록의 inner HTML을 간단하게 "No files selected!"를 표시하도록 설정합니다. `null`이 아닌 경우, 다음과 같이 파일 리스트를 구축합니다.

1. 새로운 순서가 없는 리스트(`<ul>`) 엘리먼트가 생성됩니다.
2. 새로운 리스트 엘리먼트가 `Node.appendChild()` 메소드 호출에 의해 `<div>` (en-US) 블록으로 삽입됩니다.
3. `files`에 의해 나타나는 `FileList` 내의 각 `File`에 대해 :
    1. 새로운 리스트 항목(`<li>`) 엘리먼트를 생성하고 리스트로 삽입합니다.
    2. 새로운 이미지(`<img>`) 엘리먼트를 생성합니다.
    3. `window.URL.createObjectURL()` (en-US)을 사용하여 이미지의 소스를 파일을 나타내는 새로운 객체 URL로 설정해 blob URL을 생성합니다.
    4. 이미지의 height를 60 픽셀로 설정합니다.
    5. 이미지가 로드된 이후에 더 이상 필요하지 않게되므로 객체 URL을 해제하기 위한 이미지의 로드 이벤트 핸들러를 설정합니다. `window.URL.revokeObjectURL()` (en-US) 메소드를 호출하고 `img.src`로 지정한 객체 URL 문자열을 전달하면됩니다.
    6. 리스트로 새로운 리스트 항목을 추가합니다.

다음은 위 코드의 라이브 데모입니다.

Example: Uploading a user-selected file
---------------------------------------

Another thing you might want to do is let the user upload the selected file or files (such as the images selected using the previous example) to a server. This can be done asynchronously very easily.

### Creating the upload tasks

Continuing with the code that built the thumbnails in the previous example, recall that every thumbnail image is in the CSS class `obj` with the corresponding `File` attached in a `file` attribute. This allows us to select all of the images the user has chosen for uploading using `Document.querySelectorAll()`, like this:

```js
function sendFiles() {
  const imgs = document.querySelectorAll(".obj");

  for (let i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}
```

Line 2 fetches a `NodeList`, called `imgs`, of all the elements in the document with the CSS class `obj`. In our case, these will be all of the image thumbnails. Once we have that list, it's trivial to go through it and create a new `FileUpload` instance for each. Each of these handles uploading the corresponding file.

### Handling the upload process for a file

The `FileUpload` function accepts two inputs: an image element and a file from which to read the image data.

```js
function FileUpload(img, file) {
  const reader = new FileReader();
  this.ctrl = createThrobber(img);
  const xhr = new XMLHttpRequest();
  this.xhr = xhr;

  const self = this;
  this.xhr.upload.addEventListener(
    "progress",
    function (e) {
      if (e.lengthComputable) {
        const percentage = Math.round((e.loaded \* 100) / e.total);
        self.ctrl.update(percentage);
      }
    },
    false,
  );

  xhr.upload.addEventListener(
    "load",
    function (e) {
      self.ctrl.update(100);
      const canvas = self.ctrl.ctx.canvas;
      canvas.parentNode.removeChild(canvas);
    },
    false,
  );
  xhr.open(
    "POST",
    "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php",
  );
  xhr.overrideMimeType("text/plain; charset=x-user-defined-binary");
  reader.onload = function (evt) {
    xhr.send(evt.target.result);
  };
  reader.readAsBinaryString(file);
}
```

The `FileUpload()` function shown above creates a throbber, which is used to display progress information, and then creates an `XMLHttpRequest` to handle uploading the data.

Before actually transferring the data, several preparatory steps are taken:

1. The `XMLHttpRequest`'s upload `progress` listener is set to update the throbber with new percentage information so that as the upload progresses the throbber will be updated based on the latest information.
2. The `XMLHttpRequest`'s upload `load` event handler is set to update the throbber progress information to 100% to ensure the progress indicator actually reaches 100% (in case of granularity quirks during the process). It then removes the throbber since it's no longer needed. This causes the throbber to disappear once the upload is complete.
3. The request to upload the image file is opened by calling `XMLHttpRequest`'s `open()` method to start generating a POST request.
4. The MIME type for the upload is set by calling the `XMLHttpRequest` function `overrideMimeType()`. In this case, we're using a generic MIME type; you may or may not need to set the MIME type at all depending on your use case.
5. The `FileReader` object is used to convert the file to a binary string.
6. Finally, when the content is loaded the `XMLHttpRequest` function `send()` is called to upload the file's content.
### Asynchronously handling the file upload process

This example, which uses PHP on the server side and JavaScript on the client side, demonstrates asynchronous uploading of a file.

```js
<?php
if (isset($_FILES['myFile'])) {
    // Example:
    move\_uploaded\_file($_FILES['myFile']['tmp\_name'], "uploads/" . $_FILES['myFile']['name']);
    exit;
}
?><!DOCTYPE html>
<html>
<head>
    <title>dnd binary upload</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="application/javascript">
        function sendFile(file) {
            const uri = "/index.php";
            const xhr = new XMLHttpRequest();
            const fd = new FormData();

            xhr.open("POST", uri, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    alert(xhr.responseText); // handle response.
                }
            };
            fd.append('myFile', file);
            // Initiate a multipart/form-data upload
            xhr.send(fd);
        }

        window.onload = function() {
            const dropzone = document.getElementById("dropzone");
            dropzone.ondragover = dropzone.ondragenter = function(event) {
                event.stopPropagation();
                event.preventDefault();
            }

            dropzone.ondrop = function(event) {
                event.stopPropagation();
                event.preventDefault();

                const filesArray = event.dataTransfer.files;
                for (let i=0; i<filesArray.length; i++) {
                    sendFile(filesArray[i]);
                }
            }
        }
    </script>
</head>
<body>
    <div>
        <div id="dropzone" style="margin:30px; width:500px; height:300px; border:1px dotted grey;">Drag & drop your file here...</div>
    </div>
</body>
</html>
```

Example: Using object URLs to display PDF
-----------------------------------------

Object URLs can be used for other things than just images! They can be used to display embedded PDF files or any other resources that can be displayed by the browser.

In Firefox, to have the PDF appear embedded in the iframe (rather than proposed as a downloaded file), the preference `pdfjs.disabled` must be set to `false`
비표준
.

```html
<iframe id="viewer"></iframe>
```

And here is the change of the `src` attribute:

```js
const obj_url = window.URL.createObjectURL(blob);
const iframe = document.getElementById("viewer");
iframe.setAttribute("src", obj_url);
window.URL.revokeObjectURL(obj_url);
```

Example: Using object URLs with other file types
------------------------------------------------

You can manipulate files of other formats the same way. Here is how to preview uploaded video:

```js
const video = document.getElementById("video");
const obj_url = window.URL.createObjectURL(blob);
video.src = obj_url;
video.play();
window.URL.revokeObjectURL(obj_url);
```

See also
--------

* `File`
* `FileList`
* `FileReader`
* Using XMLHttpRequest
* Using the DOM File API in chrome code
* `XMLHttpRequest`


EventSource() - Web API

EventSource()
=============

**`EventSource()`** 생성자는 원격 자원을 나타내는 새롭게 생성된 `EventSource` (en-US) 를 반환합니다.

구문
--

```js
eventSource = new EventSource(url, configuration);
```

### 파라미터

`url`

이벤트/메시지를 제공하는 원격 자원의 위치를 나타내는 `USVString` 입니다.

`configuration` Optional

새 연결 구성을 위한 옵션을 제공합니다. 가능한 항목은 다음과 같습니다.\* `withCredentials`. 기본값은 `false` 이며, CORS 가 자격을 `포함`하도록 설정되어야하는지를 나타냅니다.

예시
--

```js
var evtSource = new EventSource("sse.php");
var eventList = document.querySelector("ul");

evtSource.onmessage = function (e) {
  var newElement = document.createElement("li");

  newElement.textContent = "message: " + e.data;
  eventList.appendChild(newElement);
};
```

**참고:** GitHub 에서 전체 예시를 확인할 수 있습니다 — Simple SSE demo using PHP를 사용하는 간단한 SSE 데모를 보세요.

명세
--

| Specification |
| --- |
| HTML Standard # dom-eventsource-dev |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

함께 보기
-----

* `EventSource` (en-US)


AbortController.AbortController() - Web API

AbortController.AbortController()
=================================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

**`AbortController()`** 생성자는 새로운 `AbortController` 객체 인터페이스를 생성한다.

구문
--

```js
var controller = new AbortController();
```

### 파라미터

없음.

예제
--

다음 스니펫에서는 Fetch API를 사용해 비디오를 다운로드하는 것을 목표로 한다.

먼저 `AbortController()` 생성자를 사용해 컨트롤러를 `AbortController.signal` 프로퍼티를 사용해 `AbortSignal` 객체와 관계된 참조를 얻는다.

Fetch 요청을 시작할 때, 요청의 옵션 객체 내부에 `AbortSignal` 옵션을 전달한다(아래의 `{signal}` 참고). 이것은 신호와 컨트롤러를 fetch 요청과 관계짓고, 아래의 두 번째 이벤트 리스너에서 보여주듯이 `AbortController.abort()`를 호출하여 이를 취소할 수 있게한다.

```js
var controller = new AbortController();
var signal = controller.signal;

var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');

downloadBtn.addEventListener('click', fetchVideo);

abortBtn.addEventListener('click', function() {
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo() {
  ...
  fetch(url, {signal}).then(function(response) {
    ...
  }).catch(function(e) {
    reports.textContent = 'Download error: ' + e.message;
  })
}
```

**참고:** `abort()`가 호출되면, `fetch()` promise는 `AbortError`과 함께 reject된다.

동작하는 완전한 예제는 GitHub에서 확인 할 수 있다 — abort-api 참고(라이브 실행도 확인할 수 있다).

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-abortcontroller-abortcontroller① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

함께 보기
-----

* Fetch API


AbortController.abort() - Web API

AbortController.abort()
=======================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

`AbortController` 인터페이스의 **`abort()`** 메소드는 DOM 요청(Fetch 요청과 같은)이 완료되기 전에 취소한다. 이를 통해 fetch 요청, 모든 응답 `Body` 소비, 스트림을 취소할 수 있다.

구문
--

```js
controller.abort();
```

### 파라미터

없음.

### 반환 값

Void.

예제
--

다음 스니펫에서는 Fetch API를 사용해 비디오를 다운로드하는 것을 목표로 한다.

먼저 `AbortController()` 생성자를 사용해 컨트롤러를 `AbortController.signal` 프로퍼티를 사용해 `AbortSignal` 객체와 관계된 참조를 얻는다.

Fetch 요청을 시작할 때, 요청의 옵션 객체 내부에 `AbortSignal` 옵션을 전달한다(아래의 `{signal}` 참고). 이것은 신호와 컨트롤러를 fetch 요청과 관계짓고, 아래의 두 번째 이벤트 리스너에서 보여주듯이 `AbortController.abort()`를 호출하여 이를 취소할 수 있게한다.

```js
var controller = new AbortController();
var signal = controller.signal;

var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');

downloadBtn.addEventListener('click', fetchVideo);

abortBtn.addEventListener('click', function() {
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo() {
  ...
  fetch(url, {signal}).then(function(response) {
    ...
  }).catch(function(e) {
    reports.textContent = 'Download error: ' + e.message;
  })
}
```

**참고:** `abort()`가 호출되면, `fetch()` promise는 `AbortError`과 함께 reject된다.

동작하는 완전한 예제는 GitHub에서 확인 할 수 있다 — abort-api 참고(라이브 실행도 확인할 수 있다).

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-abortcontroller-abortcontroller① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

함께 보기
-----

* Fetch API


AbortController.signal - Web API

AbortController.signal
======================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

`AbortController` 인터페이스의 **`signal`** 읽기 전용 프로퍼티는 DOM 요청과 통신하거나 취소하는데 사용하는 `AbortSignal` 객체 인터페이스를 반환한다.

구문
--

```js
var signal = abortController.signal;
```

### 값

`AbortSignal` 객체 인터페이스.

예제
--

다음 스니펫에서는 Fetch API를 사용해 비디오를 다운로드하는 것을 목표로 한다.

먼저 `AbortController()` 생성자를 사용해 컨트롤러를 `AbortController.signal` 프로퍼티를 사용해 `AbortSignal` 객체와 관계된 참조를 얻는다.

Fetch 요청을 시작할 때, 요청의 옵션 객체 내부에 `AbortSignal` 옵션을 전달한다(아래의 `{signal}` 참고). 이것은 신호와 컨트롤러를 fetch 요청과 관계짓고, 아래의 두 번째 이벤트 리스너에서 보여주듯이 `AbortController.abort()`를 호출하여 이를 취소할 수 있게한다.

```js
var controller = new AbortController();
var signal = controller.signal;

var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');

downloadBtn.addEventListener('click', fetchVideo);

abortBtn.addEventListener('click', function() {
  controller.abort();
  console.log('Download aborted');
});

function fetchVideo() {
  ...
  fetch(url, {signal}).then(function(response) {
    ...
  }).catch(function(e) {
    reports.textContent = 'Download error: ' + e.message;
  })
}
```

**참고:** `abort()`가 호출되면, `fetch()` promise는 `AbortError`과 함께 reject된다.

동작하는 완전한 예제는 GitHub에서 확인 할 수 있다 — abort-api 참고(라이브 실행도 확인할 수 있다).

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-abortcontroller-signal② |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

함께 보기
-----

* Fetch API


도형 합성 예제 - Web API

도형 합성 예제
========

이 샘플 프로그램에서는 여러 가지 도형 합성 방법 (en-US)을 보여줍니다. 출력은 다음과 같습니다:

도형 합성 예제
--------

다음 코드에서는 프로그램의 나머지 부분에서 사용할 전역 값을 설정합니다.

```js
var canvas1 = document.createElement("canvas");
var canvas2 = document.createElement("canvas");
var gco = [
  "source-over",
  "source-in",
  "source-out",
  "source-atop",
  "destination-over",
  "destination-in",
  "destination-out",
  "destination-atop",
  "lighter",
  "copy",
  "xor",
  "multiply",
  "screen",
  "overlay",
  "darken",
  "lighten",
  "color-dodge",
  "color-burn",
  "hard-light",
  "soft-light",
  "difference",
  "exclusion",
  "hue",
  "saturation",
  "color",
  "luminosity",
].reverse();
var gcoText = [
  "기본 설정으로, 새로운 도형이 원래 도형 위에 그려집니다.",
  "새로운 도형이 원래 도형과 겹치는 부분에만 그려지며, 나머지는 투명하게 설정됩니다.",
  "새로운 도형이 원래 도형과 겹치지 않는 부분에만 그려집니다.",
  "새로운 도형이 원래 도형과 겹치는 부분에만 그려집니다.",
  "새로운 도형이 원래 도형 아래에 그려집니다.",
  "원래 도형 중 새로운 도형과 겹치는 부분이 유지되며, 나머지는 투명하게 설정됩니다.",
  "원래 도형 중 새로운 도형과 겹치지 않는 부분이 유지됩니다.",
  "원래 도형 중 새로운 도형과 겹치는 부분만 유지됩니다. 새로운 도형은 원래 도형 아래에 그려집니다.",
  "두 도형이 겹치는 곳의 색상이 두 색상값을 합한 값으로 결정됩니다.",
  "새로운 도형만 그려집니다.",
  "두 도형이 겹치는 곳이 투명하게 변하며, 나머지는 평범하게 그려집니다.",
  "위쪽 레이어의 픽셀값이 아래쪽 레이어의 해당하는 픽셀값과 곱해지며, 결과적으로 어두운 이미지가 생성됩니다.",
  "픽셀값을 뒤집고 곱한 다음 도로 뒤집습니다. 결과적으로 밝은 이미지가 생성됩니다(multiply의 반대).",
  "multiply와 screen의 조합입니다. 아래쪽 레이어의 어두운 부분은 더 어두워지고, 밝은 부분은 더 밝아집니다.",
  "두 레이어 중 어두운 픽셀값을 취합니다.",
  "두 레이어 중 밝은 픽셀값을 취합니다.",
  "아래쪽 레이어의 픽셀값을 위쪽 레이어의 뒤집힌 픽셀값으로 나눕니다.",
  "아래쪽 레이어의 뒤집힌 픽셀값을 위쪽 레이어의 픽셀값으로 나누고, 그 값을 도로 뒤집습니다.",
  "overlay와 같이 multiply와 screen의 조합이지만, 위아래 레이어의 순서가 바뀐 상태입니다.",
  "조금 더 부드러운 hard-light입니다. 완전한 검은색/흰색에서 무조건 완전한 검은색/흰색이 나오지 않습니다.",
  "한쪽 레이어의 픽셀값에서 다른 쪽 레이어의 픽셀값을 뺍니다. 빼는 순서는 결과값이 양수가 나오는 순서입니다.",
  "difference와 비슷하지만 대비가 덜합니다.",
  "아래쪽 레이어의 채도(chroma)와 명도(luma)를 보존하고 위쪽 레이어의 색상(hue)을 적용합니다.",
  "아래쪽 레이어의 색상과 명도를 보존하고 위쪽 레이어의 채도를 적용합니다.",
  "아래쪽 레이어의 명도를 보존하고 위쪽 레이어의 색상과 채도를 적용합니다.",
  "아래쪽 레이어의 색상과 채도를 보존하고 위쪽 레이어의 명도를 적용합니다.",
].reverse();
var width = 320;
var height = 340;
```

### 메인 프로그램

페이지를 불러오고 나면 다음 코드에서 예제를 준비하고 실행합니다:

```js
window.onload = function () {
  // lum in sRGB
  var lum = {
    r: 0.33,
    g: 0.33,
    b: 0.33,
  };
  // 캔버스 크기 변경
  canvas1.width = width;
  canvas1.height = height;
  canvas2.width = width;
  canvas2.height = height;
  lightMix();
  colorSphere();
  runComposite();
  return;
};
```

또한 다음 코드의 `runComposite()`가 여러 가지 작업을 처리하며, 어려운 부분은 보조 함수를 사용합니다.

```js
function createCanvas() {
  var canvas = document.createElement("canvas");
  canvas.style.background = "url(" + op_8x8.data + ")";
  canvas.style.border = "1px solid #000";
  canvas.style.margin = "5px";
  canvas.width = width / 2;
  canvas.height = height / 2;
  return canvas;
}

function runComposite() {
  var dl = document.createElement("dl");
  document.body.appendChild(dl);
  while (gco.length) {
    var pop = gco.pop();
    var dt = document.createElement("dt");
    dt.textContent = pop;
    dl.appendChild(dt);
    var dd = document.createElement("dd");
    var p = document.createElement("p");
    p.textContent = gcoText.pop();
    dd.appendChild(p);

    var canvasToDrawOn = createCanvas();
    var canvasToDrawFrom = createCanvas();
    var canvasToDrawResult = createCanvas();

    var ctx = canvasToDrawResult.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
    ctx.globalCompositeOperation = pop;
    ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
    ctx.globalCompositeOperation = "source-over";
    ctx.fillStyle = "rgba(0,0,0,0.8)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "#FFF";
    ctx.font = "14px arial";
    ctx.fillText(pop, 5, height / 2 - 5);
    ctx.restore();

    var ctx = canvasToDrawOn.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas1, 0, 0, width / 2, height / 2);
    ctx.fillStyle = "rgba(0,0,0,0.8)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "#FFF";
    ctx.font = "14px arial";
    ctx.fillText("기존 도형", 5, height / 2 - 5);
    ctx.restore();

    var ctx = canvasToDrawFrom.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.drawImage(canvas2, 0, 0, width / 2, height / 2);
    ctx.fillStyle = "rgba(0,0,0,0.8)";
    ctx.fillRect(0, height / 2 - 20, width / 2, 20);
    ctx.fillStyle = "#FFF";
    ctx.font = "14px arial";
    ctx.fillText("새로운 도형", 5, height / 2 - 5);
    ctx.restore();

    dd.appendChild(canvasToDrawOn);
    dd.appendChild(canvasToDrawFrom);
    dd.appendChild(canvasToDrawResult);

    dl.appendChild(dd);
  }
}
```

### 보조 함수

이 프로그램에서는 몇몇 보조 함수를 사용합니다.

```js
var lightMix = function () {
  var ctx = canvas2.getContext("2d");
  ctx.save();
  ctx.globalCompositeOperation = "lighter";
  ctx.beginPath();
  ctx.fillStyle = "rgba(255,0,0,1)";
  ctx.arc(100, 200, 100, Math.PI \* 2, 0, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = "rgba(0,0,255,1)";
  ctx.arc(220, 200, 100, Math.PI \* 2, 0, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = "rgba(0,255,0,1)";
  ctx.arc(160, 100, 100, Math.PI \* 2, 0, false);
  ctx.fill();
  ctx.restore();
  ctx.beginPath();
  ctx.fillStyle = "#f00";
  ctx.fillRect(0, 0, 30, 30);
  ctx.fill();
};
```

```js
var colorSphere = function (element) {
  var ctx = canvas1.getContext("2d");
  var width = 360;
  var halfWidth = width / 2;
  var rotate = (1 / 360) \* Math.PI \* 2; // per degree
  var offset = 0; // scrollbar offset
  var oleft = -20;
  var otop = -20;
  for (var n = 0; n <= 359; n++) {
    var gradient = ctx.createLinearGradient(
      oleft + halfWidth,
      otop,
      oleft + halfWidth,
      otop + halfWidth,
    );
    var color = Color.HSV\_RGB({ H: (n + 300) % 360, S: 100, V: 100 });
    gradient.addColorStop(0, "rgba(0,0,0,0)");
    gradient.addColorStop(
      0.7,
      "rgba(" + color.R + "," + color.G + "," + color.B + ",1)",
    );
    gradient.addColorStop(1, "rgba(255,255,255,1)");
    ctx.beginPath();
    ctx.moveTo(oleft + halfWidth, otop);
    ctx.lineTo(oleft + halfWidth, otop + halfWidth);
    ctx.lineTo(oleft + halfWidth + 6, otop);
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.translate(oleft + halfWidth, otop + halfWidth);
    ctx.rotate(rotate);
    ctx.translate(-(oleft + halfWidth), -(otop + halfWidth));
  }
  ctx.beginPath();
  ctx.fillStyle = "#00f";
  ctx.fillRect(15, 15, 30, 30);
  ctx.fill();
  return ctx.canvas;
};
```

```js
// HSV (1978) = H: Hue / S: Saturation / V: Value
Color = {};
Color.HSV\_RGB = function (o) {
  var H = o.H / 360,
    S = o.S / 100,
    V = o.V / 100,
    R,
    G,
    B;
  var A, B, C, D;
  if (S == 0) {
    R = G = B = Math.round(V \* 255);
  } else {
    if (H >= 1) H = 0;
    H = 6 \* H;
    D = H - Math.floor(H);
    A = Math.round(255 \* V \* (1 - S));
    B = Math.round(255 \* V \* (1 - S \* D));
    C = Math.round(255 \* V \* (1 - S \* (1 - D)));
    V = Math.round(255 \* V);
    switch (Math.floor(H)) {
      case 0:
        R = V;
        G = C;
        B = A;
        break;
      case 1:
        R = B;
        G = V;
        B = A;
        break;
      case 2:
        R = A;
        G = V;
        B = C;
        break;
      case 3:
        R = A;
        G = B;
        B = V;
        break;
      case 4:
        R = C;
        G = A;
        B = V;
        break;
      case 5:
        R = V;
        G = A;
        B = B;
        break;
    }
  }
  return {
    R: R,
    G: G,
    B: B,
  };
};

var createInterlace = function (size, color1, color2) {
  var proto = document.createElement("canvas").getContext("2d");
  proto.canvas.width = size \* 2;
  proto.canvas.height = size \* 2;
  proto.fillStyle = color1; // top-left
  proto.fillRect(0, 0, size, size);
  proto.fillStyle = color2; // top-right
  proto.fillRect(size, 0, size, size);
  proto.fillStyle = color2; // bottom-left
  proto.fillRect(0, size, size, size);
  proto.fillStyle = color1; // bottom-right
  proto.fillRect(size, size, size, size);
  var pattern = proto.createPattern(proto.canvas, "repeat");
  pattern.data = proto.canvas.toDataURL();
  return pattern;
};

var op_8x8 = createInterlace(8, "#FFF", "#eee");
```


shadow DOM 사용하기 - Web API

shadow DOM 사용하기
===============

웹 컴포넌트의 중요한 측면은 캡슐화입니다. 캡슐화를 통해 마크업 구조, 스타일, 동작을 숨기고 페이지의 다른 코드로부터의 분리하여 각기 다른 부분들이 충돌하지 않게 하고, 코드가 깔끔하게 유지될 수 있게 합니다. Shadow DOM API는 캡슐화의 핵심 파트이며, 숨겨진 분리된 DOM을 요소에 부착하는 방법을 제공합니다. 이 문서는 Shadow DOM 사용의 기본을 다룹니다.

**참고:** Shadow DOM은 Firefox (63 이상), Chrome, Opera, Safari에서 기본으로 지원됩니다. 새로운 Chromium 기반의 Edge (79 이상) 또한 Shadow DOM을 지원하나 구버전 Edge는 그렇지 않습니다.

중요 내용 보기
--------

이 문서는 여러분이 이미 DOM (Document Object Model)의 개념에 익숙하다고 가정합니다. DOM이란 마크업 문서에서 나타나는 여러 가지 요소들과 텍스트 문자열을 나타내는 연결된 노드들의 트리같은 구조입니다 (웹 문서의 경우 보통 HTML 문서). 예제로서, 다음의 HTML fragment를 고려해 보세요.

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Simple DOM example</title>
  </head>
  <body>
    <section>
      <img
 src="dinosaur.png"
 alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth." />
      <p>
        Here we will add a link to the
        <a href="https://www.mozilla.org/">Mozilla homepage</a>
      </p>
    </section>
  </body>
</html>
```

이 fragment는 다음의 DOM 구조를 생성합니다.

![](/ko/docs/Web/API/Web_components/Using_shadow_DOM/dom-screenshot.png)

*Shadow* DOM은 숨겨진 DOM 트리가 통상적인 DOM 트리에 속한 요소에 부착될 수 있게 합니다. 이 shadow DOM 트리는 shadow root로부터 시작되어 원하는 모든 요소의 안에 부착될 수 있으며, 그 방법은 일반 DOM과 같습니다.

![document, shadow root, shadow host의 상호 작용을 보여주는 SVG 버전의 그림.](/ko/docs/Web/API/Web_components/Using_shadow_DOM/shadowdom.svg)
 Flattened Tree (for rendering): (렌더링을 위해) 평평해진 트리

알아야 할 조금의 shadow DOM 용어가 있습니다.

* **Shadow host**: shadow DOM이 부착되는 통상적인 DOM 노드.
* **Shadow tree**: shadow DOM 내부의 DOM 트리.
* **Shadow boundary**: shadow DOM이 끝나고, 통상적인 DOM이 시작되는 장소.
* **Shadow root**: shadow 트리의 root 노드.

비(非) shadow 노드와 정확히 같은 방법으로 shadow DOM 내의 노드에 영향을 미칠 수 있습니다. 예를 들자면 children을 append하거나, 특성을 설정하거나, element.style.foo를 사용해 각 노드를 꾸민다거나, `<style>` 요소 내부에 있는 전체 shadow DOM 트리에 스타일을 추가하는 것이 있습니다. 차이는 shadow DOM 내부의 코드 중 아무 것도 shadow DOM 외부의 모든 것에 영향을 주지 않는다는 점인데, 이는 편리한 캡슐화를 가능케 합니다.

shadow DOM이 어떤 방법으로든 새로운 것이 아니라는 것에 주목하세요. 브라우저들은 이것을 긴 시간동안 사용해오며 요소의 내부 구조를 캡슐화했습니다. 예를 들어 기본 브라우저 컨트롤이 노출된 `<video>` 요소를 생각해 보세요. DOM에서 보이는 모든 것은 `<video>` 요소지만, 이것은 일련의 버튼들과 다른 컨트롤들을 이것의 shadow DOM 내부에 포함하고 있습니다. shadow DOM 명세서는 잘 만들어져 와서 여러분은 실제로 여러분만의 사용자 정의 요소의 shadow DOM을 조작할 수 있습니다.

기본 사용법
------

`Element.attachShadow()` (en-US) 메서드를 사용하여 어떠한 요소에든 shadow root을 부착할 수 있습니다. 이 메서드는 매개변수로 하나의 옵션을 포함하는 옵션 객체를 취합니다. 그 옵션이란 `mode` 이며, `open` 혹은 `closed` 의 값을 가집니다.

```js
let shadow = elementRef.attachShadow({ mode: "open" });
let shadow = elementRef.attachShadow({ mode: "closed" });
```

`open` 은 메인 페이지 맥락에서 작성된 JavaScript를 사용하여 shadow DOM에 접근할 수 있음을 의미합니다. 예를 들자면 `Element.shadowRoot` (en-US) 속성을 사용하여 접근할 수 있습니다.

```js
let myShadowDom = myCustomElem.shadowRoot;
```

만약 `mode: closed` 로 사용자 정의 요소에 shadow root을 부착했다면, 외부로부터 shadow DOM에 접근할 수 없을 것입니다. `myCustomElem.shadowRoot` 은 `null` 을 반환합니다. 이것은 `<video>` 와 같이 shadow DOM을 포함하고 있는 내장 요소들의 경우입니다.

**참고:** 이 블로그 글이 보여주듯, closed인 shadow DOM을 우회하는 것은 사실 상당히 쉽고, shadow DOM을 완전히 숨기기 위한 귀찮은 일은 종종 그 일의 가치보다 더 큽니다.

만약 shadow DOM을 사용자 정의 요소에 사용자 정의 요소 생성자의 일부로써 부착한다면 (단연코 shadow DOM의 가장 유용한 적용), 다음과 같이 할 것입니다.

```js
let shadow = this.attachShadow({ mode: "open" });
```

shadow DOM을 요소에 부착했을 때, shadow DOM을 조작하는 것은 단지 통상적인 DOM 조작에 사용되는 것과 같은 DOM API를 사용하는 것의 문제입니다.

```js
let para = document.createElement("p");
shadow.appendChild(para);
// 등등
```

간단한 예제 살펴보기
-----------

이제 사용자 정의 요소 내부에서 작동하는 shadow DOM을 시연하기 위한 간단한 예제 속으로 들어가 봅시다. `<popup-info>` (작동 예제도 볼 수 있습니다). 이것은 이미지 아이콘과 텍스트 문자열을 취하고, 아이콘을 페이지에 넣습니다. 아이콘이 포커스되었을 때, 이것은 텍스트를 팝업 정보 박스에 표시하여 추가적인 맥락 내 정보를 제공합니다. 우선, JavaScript 파일에서 `PopUpInfo` 라는 클래스를 정의하는데, 이 클래스는 `HTMLElement` 를 확장합니다.

```js
class PopUpInfo extends HTMLElement {
  constructor() {
    // 항상 super를 생성자에서 먼저 호출합니다
    super();

    // 요소 기능을 여기 작성합니다

    ...
  }
}
```

클래스 정의 내부에서 요소의 생성자를 정의하는데, 이는 이 클래스의 인스턴스가 인스턴스화되었을 때 요소가 가질 모든 기능을 정의합니다.

### shadow root 생성하기

첫번째로 shadow root을 사용자 정의 요소에 부착합니다.

```js
// shadow root을 생성합니다
let shadow = this.attachShadow({ mode: "open" });
```

### shadow DOM 구조를 생성합니다

다음으로, 몇 가지 DOM 조작을 사용하여 요소의 내부 shadow DOM 구조를 생성합니다.

```js
// span들을 생성합니다
let wrapper = document.createElement("span");
wrapper.setAttribute("class", "wrapper");
let icon = document.createElement("span");
icon.setAttribute("class", "icon");
icon.setAttribute("tabindex", 0);
let info = document.createElement("span");
info.setAttribute("class", "info");

// 특성의 내용을 취하고 그것을 info span 내부에 넣습니다
let text = this.getAttribute("data-text");
info.textContent = text;

// 아이콘을 삽입합니다
let imgUrl;
if (this.hasAttribute("img")) {
  imgUrl = this.getAttribute("img");
} else {
  imgUrl = "img/default.png";
}
let img = document.createElement("img");
img.src = imgUrl;
icon.appendChild(img);
```

### shadow DOM 꾸미기

이후에 `<style>` 요소를 생성하고 shadow DOM을 꾸미기 위한 몇 가지 CSS로 style 요소를 채웁니다.

```js
// shadow DOM에 적용할 몇 가지 CSS를 생성합니다
let style = document.createElement("style");

style.textContent = `
.wrapper {
 position: relative;
}

.info {
 font-size: 0.8rem;
 width: 200px;
 display: inline-block;
 border: 1px solid black;
 padding: 10px;
 background: white;
 border-radius: 10px;
 opacity: 0;
 transition: 0.6s all;
 position: absolute;
 bottom: 20px;
 left: 10px;
 z-index: 3;
}

img {
 width: 1.2rem;
}

.icon:hover + .info, .icon:focus + .info {
 opacity: 1;
}`;
```

### shadow DOM을 shadow root에 부착하기

마지막 단계는 생성된 모든 요소를 shadow root에 부착하는 것입니다.

```js
// 생성된 요소들을 shadow dom에 부착합니다
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
```

### 사용자 정의 요소 사용하기

한 번 클래스가 정의되고 나면, 요소를 사용하는 것은 요소를 정의하는 것과 페이지에 요소를 추가하는 것만큼이나 간단합니다. (페이지에 요소를 추가하는 것은 사용자 정의 요소 사용하기에서 설명되었습니다).

```js
// 새로운 요소를 정의합니다
customElements.define("popup-info", PopUpInfo);
```

```html
<popup-info
 img="img/alt.png"
 data-text="Your card validation code (CVC) is an extra
 security feature — it is the last 3 or 4
 numbers on the back of your card."></popup-info>
```

### 내부 스타일 대 외부 스타일

위의 예제에서는 `<style>` 요소를 사용하여 Shadow DOM에 스타일을 적용했지만, 대신 `<link>` 요소로부터 외부 스타일시트를 참조함으로써 스타일을 적용하는 것도 완벽히 가능합니다.

예를 들어, popup-info-box-external-stylesheet 예제 (소스 코드도 볼 수 있습니다) 의 코드를 확인해 보세요.

```js
// 외부 스타일을 shadow dom에 적용합니다
const linkElem = document.createElement("link");
linkElem.setAttribute("rel", "stylesheet");
linkElem.setAttribute("href", "style.css");

// 생성된 요소를 shadow dom에 적용합니다
shadow.appendChild(linkElem);
```

`<link>` 요소는 shadow root의 페인트를 막지 않아, 스타일시트가 로딩되는 동안 스타일되지 않은 내용의 번쩍임 (FOUC, flash of unstyled content) 이 있을 수 있다는 것에 주의하세요.

많은 모던 브라우저들은 공통 노드로부터 복제되었거나 동일한 텍스트를 가지고 있는 `<style>` 태그에 대한 최적화를 구현하여 스타일 태그가 하나의 백업 스타일시트를 공유할 수 있게 합니다. 이 최적화로 인해 외부 스타일과 내부 스타일의 성능은 비슷할 것입니다.

같이 보기
-----

* 사용자 정의 요소 사용하기
* template와 slot 사용하기 (en-US)


사용자 정의 요소 사용하기 - Web API

사용자 정의 요소 사용하기
==============

웹 컴포넌트 표준의 주요 기능 중 하나는 사용자 정의 페이지 기능을 제공하는 길고 중첩된 요소들의 묶음으로 만족하는 것보다는, HTML 페이지에서 기능을 캡슐화하는 사용자 정의 요소를 생성하는 능력입니다. 이 문서는 Custom Elements API의 사용을 소개합니다.

중요 내용 보기
--------

웹 document의 사용자 정의 요소의 컨트롤러는 `CustomElementRegistry` 객체입니다. 이 객체는 페이지에 사용자 정의 요소를 등록할 수 있게 하고, 어떤 사용자 정의 요소가 등록되었는지 등에 대한 정보를 반환합니다.

페이지에 사용자 정의 요소를 등록하는 방법은 `CustomElementRegistry.define()` (en-US) 메서드를 사용하는 것입니다. 이 메서드는 인자로 다음을 취합니다.

* `DOMString`은 요소에 주는 이름을 나타냅니다. 사용자 정의 요소의 이름은 대시가 사용되는 것을 요구한다는 것에 주의하세요 (kebab-case). 이름은 하나의 단어일 수 없습니다.
* 요소의 동작을 정의하는 class 객체.
* Optional `extends` 속성을 포함하는 옵션 객체인데, 이는 이 요소가 상속받는 내장 요소가 있다면, 그 내장 요소를 명시합니다 (오직 사용자 정의된 내장 요소에만 관계있습니다. 아래의 정의를 보세요).

예를 들어, 사용자 정의 단어 카운트 요소를 다음과 같이 정의할 수 있습니다.

```js
customElements.define("word-count", WordCount, { extends: "p" });
```

이 요소는 `word-count` 라고 불리며, 이것의 클래스 객체는 `WordCount` 이고, 이것은 `<p>`요소를 확장합니다.

사용자 정의 요소의 클래스 객체는 표준 ES 2015 class 구문을 사용하여 작성됩니다. 에를 들어, `WordCount` 는 다음과 같이 구조화될 수 있습니다.

```js
class WordCount extends HTMLParagraphElement {
  constructor() {
    // 항상 super를 생성자에서 먼저 호출합니다
    super();

    // 요소 기능은 여기 작성됩니다

    ...
  }
}
```

이것은 단순히 간단한 예제이지만, 여기서 할 수 있는 더 많은 것이 있습니다. 클래스 내부에서 특정한 생명 주기 콜백을 정의할 수 있는데, 이 콜백은 요소의 생명 주기의 특정한 지점에서 실행됩니다. 예를 들어, `connectedCallback` 은 사용자 정의 요소가 문서에 연결된 요소에 추가될 때마다 호출되는 반면, `attributeChangedCallback` 은 사용자 정의 요소의 특성 중 하나가 추가되거나, 제거되거나, 변경될 때 호출됩니다.

아래의 생명 주기 콜백 사용하기 섹션에서 더 많은 것을 배울 수 있습니다.

두 종류의 사용자 정의 요소가 있습니다.

* **독립적인 사용자 정의 요소**는 독립적입니다. 이 유형의 요소는 표준 HTML 요소를 상속받지 않습니다. 이러한 요소는 페이지에서 말 그대로 HTML 요소로 작성됨으로써 사용됩니다. 예를 들어 `<popup-info>`, 혹은 `document.createElement("popup-info")`.
* **사용자 정의된 내장 요소**는 기본 HTML 요소를 상속받습니다. 이러한 요소를 생성하기 위해서는, (위의 예제에서 암시되었듯이) 어떤 요소를 이것이 확장하는지 명시해야 하며, 이러한 요소는 기본 요소를 작성함으로써 사용되나 `is` 특성 (혹은 속성) 에 사용자 정의 요소의 이름을 명시해야 합니다. 예를 들어 `<p is="word-count">`, 혹은 `document.createElement("p", { is: "word-count" })`.
몇 가지 간단한 예제 살펴보기
----------------

이 지점에서, 어떻게 사용자 정의 요소가 생성되는지를 자세히 보여주는 몇 가지 간단한 예제를 살펴봅시다.

### 독립적인 사용자 정의 요소

독립적인 사용자 정의 요소의 예제를 살펴봅시다. `<popup-info-box>` (작동 예제도 볼 수 있습니다). 이것은 이미지 아이콘과 텍스트 문자열을 취하고, 아이콘을 페이지에 넣습니다. 아이콘이 포커스되었을 때, 이것은 텍스트를 팝업 정보 박스에 표시하여 추가적인 맥락 내 정보를 제공합니다.

우선, JavaScript 파일에서 `PopUpInfo` 라는 클래스를 정의하는데, 이 클래스는 포괄적인 `HTMLElement` 클래스를 확장합니다.

```js
class PopUpInfo extends HTMLElement {
  constructor() {
    // 항상 super를 생성자에서 먼저 호출합니다
    super();

    // 요소 기능을 여기 작성합니다

    ...
  }
}
```

앞선 코드 스니펫은 클래스에 대한 `constructor()` 정의를 포함하고 있는데, 이는 항상 `super()`를 호출함으로써 시작하여 올바른 프로토타입 체인이 확립되도록 합니다.

생성자 내부에서, 클래스의 인스턴스가 인스턴스화되었을 때 요소가 가질 모든 기능을 정의합니다. 이 경우 우리는 shadow root을 사용자 정의 요소에 부착하고, 몇 가지 DOM 조작을 사용하여 요소의 내부 shadow DOM 구조를 생성하는데, 이는 그리고서 shadow root에 부착됩니다. 그리고 마지막으로 몇 가지 CSS를 shadow root에 부착하여 shadow DOM을 꾸밉니다.

```js
// shadow root을 생성합니다
this.attachShadow({ mode: "open" }); // 'this.shadowRoot'을 설정하고 반환합니다

// (중첩된) span 요소들을 생성합니다
const wrapper = document.createElement("span");
wrapper.setAttribute("class", "wrapper");
const icon = wrapper.appendChild(document.createElement("span"));
icon.setAttribute("class", "icon");
icon.setAttribute("tabindex", 0);
// 정의된 특성으로부터의 아이콘 혹은 기본 아이콘을 삽입합니다
const img = icon.appendChild(document.createElement("img"));
img.src = this.hasAttribute("src")
  ? this.getAttribute("src")
  : "img/default.png";

const info = wrapper.appendChild(document.createElement("span"));
info.setAttribute("class", "info");
// 특성의 내용을 취하고 그것을 info span 내부에 넣습니다
info.textContent = this.getAttribute("data-text");

// shadow dom에 적용할 몇 가지 CSS를 생성합니다
const style = document.createElement("style");
style.textContent =
  ".wrapper {" +
  // 간결함을 위해 CSS 생략됨

  // 생성된 요소들을 shadow DOM에 부착합니다
  this.shadowRoot.append(style, wrapper);
```

마지막으로, `CustomElementRegistry` 에 사용자 정의 요소를 앞에서 언급된 `define()` 메서드를 사용해 등록합니다. 매개변수에서 요소의 이름과, 그리고 나서 요소의 기능을 정의하는 클래스명을 명시합니다.

```js
customElements.define("popup-info", PopUpInfo);
```

이 요소는 이제 페이지에서 사용 가능합니다. HTML 전체에서, 요소를 다음과 같이 사용합니다.

```html
<popup-info
 img="img/alt.png"
 data-text="Your card validation code (CVC)
 is an extra security feature — it is the last 3 or 4 numbers on the
 back of your card."></popup-info>
```

**참고:** 전체 JavaScript 소스 코드를 여기서 확인할 수 있습니다.

### 내부 스타일 대 외부 스타일

상기의 예제에서 `<style>` 요소가 사용되어 Shadow DOM에 스타일을 적용했으나, 대신 `<link>` 요소로부터 외부 스타일시트를 참조함으로써 스타일을 적용하는 것도 완벽히 가능합니다.

예를 들자면, popup-info-box-external-stylesheet 예제에서 이 코드를 확인해 보세요 (소스 코드도 볼 수 있습니다).

```js
// 외부 스타일을 shadow dom에 적용하기
const linkElem = document.createElement("link");
linkElem.setAttribute("rel", "stylesheet");
linkElem.setAttribute("href", "style.css");

// 생성된 요소를 shadow dom에 부착하기
shadow.appendChild(linkElem);
```

`<link>` 요소는 shadow root의 페인트를 막지 않아, 스타일시트가 로딩되는 동안 스타일되지 않은 내용의 번쩍임 (FOUC, flash of unstyled content) 이 있을 수 있다는 점에 주의하세요.

많은 모던 브라우저들은 공통 노드로부터 복제되었거나 동일한 텍스트를 가지고 있는 `<style>` 태그에 대한 최적화를 구현하여 스타일 태그가 하나의 백업 스타일시트를 공유할 수 있게 합니다. 이 최적화로 인해 외부 스타일과 내부 스타일의 성능은 비슷할 것입니다.

### 사용자 정의된 내장 요소

이제 사용자 정의된 요소 예제를 살펴봅시다. expanding-list (작동 예제도 확인해 보세요). 이것은 정렬되지 않은 리스트를 확장/축소 메뉴로 바꿔 줍니다.

우선, 요소의 클래스를 이전과 같은 방식으로 정의합니다.

```js
class ExpandingList extends HTMLUListElement {
  constructor() {
    // 항상 super를 생성자에서 먼저 호출합니다
    super();

    // 요소 기능을 여기 작성합니다

    ...
  }
}
```

요소 기능을 여기서는 자세히 설명하지 않을 것이지만, 소스 코드를 확인해서 어떻게 작동하는지 발견할 수 있을 것입니다. 여기서의 차이는 이 요소가 `HTMLElement`가 아니라, `HTMLUListElement` (en-US) 인터페이스를 확장한다는 것입니다. 그래서 이 요소는 독립된 요소이기보다는 `<ul>` 요소의 모든 특성을 가지고 있으며 그 위에 우리가 정의한 기능 또한 가지고 있습니다. 이것이 이 요소를 독립적인 요소보다는 사용자 정의된 내장 요소로 만들어주는 것입니다.

다음으로, 전과 같이 `define()` 메서드를 사용하여 요소를 등록하나, 이번엔 이 사용자 정의 요소가 어떤 요소를 상속받는지를 나타내는 옵션 객체를 포함합니다.

```js
customElements.define("expanding-list", ExpandingList, { extends: "ul" });
```

웹 document에서 이 내장 요소를 사용하는 것은 또한 어느 정도 다르게 보입니다.

```html
<ul is="expanding-list">
  ...
</ul>
```

`<ul>` 요소를 평범하게 사용하나, `is` 특성 내부에 사용자 정의 요소의 이름을 명시합니다.

**참고:** 전체 JavaScript 소스 코드를 확인해볼 수 있습니다.

생명 주기 콜백 사용하기
-------------

몇 가지 다른 콜백을 사용자 정의 요소의 클래스 정의 내부에 정의할 수 있는데, 이 콜백들은 요소의 생명 주기의 각기 다른 지점에서 발생됩니다.

* `connectedCallback`: 사용자 정의 요소가 문서에 연결된 요소에 추가될 때마다 호출됩니다. 이것은 노드가 이동될 때마다 발생할 것이며, 요소의 내용이 완전히 해석되기 전에 발생할 지도 모릅니다.

**참고:** `connectedCallback` 은 요소가 더 이상 연결되지 않았을 때 호출될 수도 있으므로, 확실하게 하기 위해선 `Node.isConnected` (en-US)를 사용하세요.
* `disconnectedCallback`: 사용자 정의 요소가 document의 DOM에서 연결 해제되었을 때마다 호출됩니다.
* `adoptedCallback`: 사용자 정의 요소가 새로운 document로 이동되었을 때마다 호출됩니다.
* `attributeChangedCallback`: 사용자 정의 요소의 특성들 중 하나가 추가되거나, 제거되거나, 변경될 때마다 호출됩니다. 어떤 특성이 변경에 대해 알릴지는 static get `observedAttributes` 메서드에서 명시됩니다.

이것들의 사용례를 봅시다. 아래의 코드는 life-cycle-callbacks 예제에서 취해졌습니다 (작동 예제도 볼 수 있습니다). 이것은 페이지에 고정된 크기의 색이 칠해진 사각형을 생성하는 작은 예제입니다. 사용자 정의 요소는 다음과 같이 생겼습니다.

```html
<custom-square l="100" c="red"></custom-square>
```

클래스 생성자는 정말로 간단합니다. shadow DOM을 요소에 부착하고, 빈 `<div>` (en-US) 와 `<style>` 요소를 shadow root에 부착합니다.

```js
const shadow = this.attachShadow({ mode: "open" });

const div = document.createElement("div");
const style = document.createElement("style");
shadow.appendChild(style);
shadow.appendChild(div);
```

이 예제에서의 핵심 함수는 `updateStyle()` 입니다. 이 함수는 요소를 취하고, 요소의 shadow root을 얻고, shadow root의 `<style>` 요소를 찾고, `width`, `height`, `background-color`를 스타일에 추가합니다.

```js
function updateStyle(elem) {
  const shadow = elem.shadowRoot;
  shadow.querySelector("style").textContent = `
 div {
 width: ${elem.getAttribute("l")}px;
 height: ${elem.getAttribute("l")}px;
 background-color: ${elem.getAttribute("c")};
 }
 `;
}
```

실제 갱신은 모두 생명 주기 콜백에 의해 다뤄지는데, 이 콜백들은 클래스 정의 내부에 메서드로 위치합니다. `connectedCallback()` 은 요소가 DOM에 추가될 때마다 실행됩니다. 여기서 `updateStyle()` 함수가 실행되어 사각형이 이것의 특성에 정의된 대로 꾸며지도록 합니다.

```js
connectedCallback() {
  console.log('Custom square element added to page.');
  updateStyle(this);
}
```

`disconnectedCallback()` 과 `adoptedCallback()` 콜백은 콘솔에 간단한 메시지를 로그하여 언제 요소가 DOM에서 제거되었는지, 혹은 다른 페이지로 이동되었는지를 알립니다.

```js
disconnectedCallback() {
  console.log('Custom square element removed from page.');
}

adoptedCallback() {
  console.log('Custom square element moved to new page.');
}
```

`attributeChangedCallback()` 은 요소의 특성 중 하나가 어떠한 방식으로 변경될 때마다 실행됩니다. 이 콜백의 매개변수에서 볼 수 있다시피, 특성의 이름, 이전 값, 새로운 값을 보고 특성의 변경 사항에 개별적으로 대응하는 게 가능합니다. 그러나, 이번 경우에는 개별적으로 대응하지 않고, 사각형의 스타일이 새로운 값에 따라 갱신될 수 있도록 단순히 `updateStyle()` 함수를 다시 호출합니다.

```js
attributeChangedCallback(name, oldValue, newValue) {
  console.log('Custom square element attributes changed.');
  updateStyle(this);
}
```

`attributeChangedCallback()` 콜백이 특성이 변경되었을 때 발생되기 하기 위해서는, 해당 특성을 관찰해야만 합니다. 이 작업은 `static get observedAttributes()` 메서드를 사용자 정의 요소 클래스 내부에 명시함으로써 이루어집니다. 이 메서드는 관찰하기를 원하는 특성들의 이름을 포함하는 배열을 `return` 해야 합니다.

```js
static get observedAttributes() { return ['c', 'l']; }
```

이 예제에서 이 메서드는 생성자의 바로 위에 위치해 있습니다.

**참고:** 전체 JavaScript 소스 코드를 확인해 보세요.

트랜스파일러 대 클래스
------------

ES2015 class는 레거시 브라우저를 목표로 하는 Babel 6 또는 TypeScript 에서 신뢰할만하게 트랜스파일될 수 없다는 점에 주의하세요. Babel 7을 사용하거나 Babel 6에 대해서 babel-plugin-transform-builtin-classes를 사용할 수 있고, 레거시 대신에 TypeScript의 ES2015를 목표로 할 수 있습니다.

라이브러리
-----

사용자 정의 요소를 생성할 때 추상화의 단계를 올리는 것을 목표로 하는 웹 컴포넌트를 기반으로 하는 몇 가지의 라이브러리들이 있습니다. 이러한 라이브러리에는 FASTElement, snuggsi, X-Tag, Slim.js, Lit, Smart, Stencil, hyperHTML-Element, DataFormsJS, Custom-Element-Builder가 있습니다.


Window: unload 이벤트 - Web API

Window: unload 이벤트
==================

**`unload`** 이벤트는 문서나 하위 리소스가 언로딩 중일 때 발생합니다.

| 확산 | 아니오 |
| --- | --- |
| 취소 가능 | 아니오 |
| 인터페이스 | `Event` |
| 이벤트 처리기 속성 | `onunload` (en-US) |

`unload`는 다음 이벤트 이후 발생합니다.

* `beforeunload` (취소 가능한 이벤트)
* `pagehide` (en-US)

`unload` 시점의 문서는 다음과 같은 상태입니다.

* 이미지, IFrame 등 모든 리소스는 여전히 존재합니다.
* 최종 사용자는 아무것도 볼 수 없습니다.
* UI 상호작용은 아무 효과도 없습니다. (`window.open()` (en-US), `window.alert()`, `window.confirm()`, 등등)
* 오류가 발생해도 언로딩 절차는 중단되지 않습니다.

참고로 `unload` 이벤트 역시 문서 트리의 순서를 따라갑니다. 즉 부모 프레임의 `unload`가 자식 프레임의 `unload` **이전에** 발생합니다. 아래 예제를 확인하세요

예제
--

```html
<!doctype html>
<html>
  <head>
    <title>Parent Frame</title>
    <script>
 window.addEventListener("beforeunload", function (event) {
 console.log("I am the 1st one.");
 });
 window.addEventListener("unload", function (event) {
 console.log("I am the 3rd one.");
 });
 </script>
  </head>
  <body>
    <iframe src="child-frame.html"></iframe>
  </body>
</html>
```

아래는 `child-frame.html`의 내용입니다.

```html
<!doctype html>
<html>
  <head>
    <title>Child Frame</title>
    <script>
 window.addEventListener("beforeunload", function (event) {
 console.log("I am the 2nd one.");
 });
 window.addEventListener("unload", function (event) {
 console.log("I am the 4th and last one…");
 });
 </script>
  </head>
  <body>
    ☻
  </body>
</html>
```

부모 프레임이 언로딩 될 때, `console.log()` 메시지를 통해 순서를 확인할 수 있습니다.

명세
--

| Specification |
| --- |
| HTML Standard # event-unload |
| HTML Standard # handler-window-onunload |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* 관련 이벤트: `DOMContentLoaded`, `readystatechange`, `load`
* Unloading Documents — unload a document


Window: beforeunload 이벤트 - Web API

Window: beforeunload 이벤트
========================

**`beforeunload`** 이벤트는 window, 문서(document) 및 해당 리소스가 언로드(unload) 되려고 할 때 시작됩니다. 문서는 계속 보이고 있으며 이벤트는 이 시점에서도 취소할 수 있습니다.

이 이벤트를 사용하면 웹 페이지에서 사용자에게 실제로 페이지를 떠날 것인지 묻는 확인 대화 상자를 표시할 수 있습니다. 사용자가 확인 버튼을 누를 경우 브라우저는 새 페이지로 이동하고 그렇지 않으면 탐색을 취소하고 현재 페이지에 머무릅니다.

명세에 따라 확인 대화 상자를 표시하려면 이벤트 핸들러가 이벤트에서 `preventDefault()`를 호출해야 합니다.

HTML 명세에는 이 이벤트 동안 `window.alert()`, `window.confirm()`, 및 `window.prompt()` 메서드에 대한 호출이 무시될 수 있다고 명시되어 있습니다. 자세한 내용은 HTML 명세을 참고하세요.

구문
--

`addEventListener()`와 같은 메서드에서 이벤트 이름을 사용하거나 이벤트 핸들러 속성을 설정합니다.

```js
addEventListener("beforeunload", (event) => {});
onbeforeunload = (event) => {};
```

이벤트 타입
------

`BeforeUnloadEvent` (en-US) 이벤트는 `Event`에서 상속됩니다.

이벤트 처리기 별칭(alias)
-----------------

`Window` 인터페이스 뿐만 아니라 이벤트 핸들러 속성 `onbeforeunload`도 사용할 수 있습니다.

* `HTMLBodyElement` (en-US)
* `HTMLFrameSetElement` (en-US)
* `SVGSVGElement` (en-US)
보안
--

임시 사용자 활성화가 필요합니다. beforeunload 이벤트가 동작하기 위해선 사용자가 페이지 또는 UI 요소와 상호 작용해야 합니다.

사용 일람
-----

`beforeunload` 이벤트는 `unload` 이벤트와 동일한 문제가 있습니다.

특히 모바일에서 `beforeunload` 이벤트는 안정적으로 실행되지 않습니다. 예를 들면, 다음의 시나리오에서는 `beforeunload` 이벤트가 전혀 발생하지 않습니다.

1. 모바일 사용자가 페이지를 방문합니다.
2. 그런 다음 사용자가 다른 앱으로 전환합니다.
3. 나중에 사용자는 앱 관리자에서 브라우저를 닫습니다.

`beforeunload` 이벤트는 back/forward cache (bfcache)와 호환되지 않습니다. 이 이벤트를 사용하는 많은 페이지가 이벤트 후에 페이지가 더 이상 존재하지 않을 것이라고 가정하기 때문입니다. 이를 방지하기 위해 브라우저는 `beforeunload` 수신기(listener)가 있는 경우 bfcache에 페이지를 배치하지 않으며 이는 성능에 좋지 않습니다.

그러나 `unload` 이벤트와 달리 `beforeunload` 이벤트에는 합법적인 사용 사례가 있습니다. 사용자가 페이지에서 데이터를 입력후 저장하지 않고 이탈(unload) 할때 데이터가 손실되는 경우의 시나리오입니다.

개발자는 성능에 미치는 영향을 최소화하기 위해 이 시나리오에서 실제로 저장되지 않는 변경 사항이 있을 때만 `beforeunload`를 수신하는 것이 좋습니다. 이에 대한 예는 아래의 예제 섹션을 참조하세요.

`beforeunload` 이벤트와 관련된 문제에 대한 더 많은 정보는 페이지 라이프사이클 API 가이드를 참고하세요.

예제
--

이 예제에서 페이지는 `input` 텍스트 (en-US)에 대한 변경 사항을 수신합니다. 만약 요소의 값이 포함되어 있으면 `beforeunload`에 대한 수신기(listener)를 추가합니다. 만약 요소가 비어있으면 수신기를 제거합니다.

```js
const beforeUnloadListener = (event) => {
  event.preventDefault();
  return (event.returnValue = "");
};
const nameInput = document.querySelector("#name");
nameInput.addEventListener("input", (event) => {
  if (event.target.value !== "") {
    addEventListener("beforeunload", beforeUnloadListener, { capture: true });
  } else {
    removeEventListener("beforeunload", beforeUnloadListener, {
      capture: true,
    });
  }
});
```

명세
--

| Specification |
| --- |
| HTML Standard # event-beforeunload |
| HTML Standard # handler-window-onbeforeunload |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

`WindowEventHandlers.onbeforeunload` (en-US)에서 각각의 브라우저가 beforeunload를 어떻게 처리하는지 자세히 알아보세요.

호환성 참고 사항
---------

 HTML 명세에는 개발자가 `Event.returnValue` (en-US)를 사용하여 사용자에게
 메시지를 표시하는 대신 `Event.preventDefault()` 메서드를 사용해야 한다고
 명시되어 있습니다. 그러나 아직 모든 브라우저에서 지원하고 있지는 않습니다.

 이 이벤트가 `null` 또는 `undefined` 이외의 값을 반환(또는 `returnValue` 속성을 설정)하면
 페이지 언로드를 확인하라는 메시지가 사용자에게 표시됩니다. 오래된 브라우저에서는 이벤트의
 반환 값이 표시됩니다. Firefox 44, Chrome 51, Opera 38, 및 Safari 9.1 이후로 반환된 문자열 대신
 웹 페이지에서 제어할 수 없는 문자열이 표시됩니다. 예를들면 아래와 같습니다.

* Firefox는 "This page is asking you to confirm that you want to leave - data you have entered may not be saved."라는 문자열을 표시합니다(Firefox 버그 588292를 확인하세요).
* Chrome은 "Do you want to leave the site? Changes you made may not be saved."라는 문자열을 표시합니다(Chrome 플랫폼 상태를 확인하세요).

 일부 브라우저에서 `window.alert()`,
 `window.confirm()`, 및 `window.prompt()` 호출은 beforeunload 이벤트가
 진행되는 동안 무시될 수 있습니다. 자세한 내용은 HTML 명세를 확인하세요.

 또한 다양한 브라우저는 이벤트 결과를 무시하고 사용자에게 확인을 전혀 요청하지 않습니다. 이러한 경우
 문서는 항상 자동으로 언로드 됩니다. Firefox는 이 동작을 활성화 하기 위해 *about:config* 에
 `dom.disable_beforeunload`라는 스위치가 있습니다. Chrome 60부터는 프레임이나 페이지가 로드된 이후
 사용자가 제스처를 수행하지 않은 경우 확인을 건너뜁니다.
 페이지에서 F5를 누르는 것은 사용자 상호작용으로 간주되는 반면 새로고침 화살표를 마우스로 클릭하거나
 Chrome DevTools에 포커스가 있는 상태에서 F5를 누르는 것은 사용자 상호작용으로 간주되지 않습니다
 (Chrome 81 기준입니다).

같이 보기
-----

* 관련 이벤트: `DOMContentLoaded`, `readystatechange`, `load`, `unload`
* Unloading Documents — Prompt to unload a document
* Remove Custom Messages in onbeforeload Dialogs after Chrome 51
* Don't lose user and app state, use Page Visibility는 `beforeunload`/`unload`가 아닌 `visibilitychange`를 사용해야 하는 이유를 자세히 설명합니다.
* Page Lifecycle API는 웹 애플리케이션에서 페이지 수명 주기 동작을 처리하는 모범 사례 지침을 제공합니다.
* PageLifecycle.js는 페이지 생명 주기 동작의 브라우저간 불일치를 처리하는 JavaScript 라이브러리입니다.
* Back/forward cache는 back/forward 캐시가 무엇이며 다양한 페이지 수명 주기 이벤트에 미치는 영향을 설명합니다.


Window: message 이벤트 - Web API

Window: message 이벤트
===================

`message` 이벤트는 window가 메세지를 받을 때, 예를 들어 다른 브라우징 맥락에서 `Window.postMessage()`을 호출할 때 `Window` 객체에서 실행됩니다.

이 이벤트는 취소할 수 없고, 버블링되지도 않습니다.

구문
--

`addEventListener()`와 같은 메서드에서 이벤트 이름을 사용하거나, 이벤트 핸들러의 속성을 설정하세요.

```js
addEventListener("message", (event) => {});

onmessage = (event) => {};
```

이벤트 타입
------

`Event`로부터 상속된 `MessageEvent`입니다.

Event

MessageEvent이벤트 속성
------

*이 인터페이스는 부모인 `Event`의 속성들도 상속받습니다.*

`MessageEvent.data` (en-US) 읽기 전용

발신자가 보낸 메세지 데이터입니다.

`MessageEvent.origin` (en-US) 읽기 전용

발신자의 출처를 나타내는 문자열입니다.

`MessageEvent.lastEventId` (en-US) 읽기 전용

이벤트의 고유한 ID 값을 나타내는 문자열입니다.

`MessageEvent.source` (en-US) 읽기 전용

발신인을 나타내는 `MessageEventSource`(WindowProxy, `MessagePort` (en-US), `ServiceWorker` 등등) 입니다.

`MessageEvent.ports` (en-US) 읽기 전용

메세지가 전송되는 채널과 연결된 포트를 나타내는 `MessagePort` (en-US) 객체의 배열입니다(공유 워커에 메세지를 보내거나, 채널 메세징에 적합합니다).

예시
--

서로 다른 `<iframe>` (en-US)과 같은, 다른 브라우징 맥락에서 메세지를 보낸다고 가정할 때, 아래와 같이 코드를 작성하면 됩니다.

```js
const targetFrame = window.top.frames[1];
const targetOrigin = "https://example.org";
const windowMessageButton = document.querySelector("#window-message");

windowMessageButton.addEventListener("click", () => {
  targetFrame.postMessage("hello there", targetOrigin);
});
```

수신자는 `addEventListener()`를 사용해, 아래 코드와 같이 메세지를 받을 수 있습니다.

```js
window.addEventListener("message", (event) => {
  console.log(`Received message: ${event.data}`);
});
```

다른 방법으로는 `onmessage`라는 이벤트 핸들러의 속성을 이용할 수 있습니다.

```js
window.onmessage = (event) => {
  console.log(`Received message: ${event.data}`);
};
```

명세서
---

| Specification |
| --- |
| HTML Standard # event-message |
| HTML Standard # handler-window-onmessage |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* 연관된 이벤트들: `messageerror` (en-US).
* `Window.postMessage()`.