아빠는 개발자

타노스 이스터 에그 들여다보기

May 10, 2019

Translated by readers into: English

어벤져스 인피니티 워 엔드게임 개봉과 함께 구글은 검색 결과에 이스터 에그를 추가했다. 구글에서 ‘타노스(Thanos)‘를 검색하면 오른쪽에 인피니티 건틀릿 아이콘이 보이고, 이를 클릭하면 검색 결과의 절반이 순차적으로 가루가 되어 사라진다.

이스터 에그를 보던 아들이 “아빠도 저런 거 할 수 있어?”라고 묻는다. “아마도?”라고 대답하긴 했는데 막상 구현하려니 어떻게 구현해야 할지 아이디어가 떠오르질 않는다. 결국 브라우저의 개발자 도구를 사용하여 어떻게 작동하는지 살펴보았다. 이스터 에그가 화려해서 복잡할 것으로 예상했지만, 예상과 달리 매우 간단하게 구현되어있었다.

데모를 보려면, 오른쪽의 인피니티 건틀릿을 클릭하세요.

avengers

BLACK WIDOW

avengers

THOR



다음 세 단계로 웹 문서를 가루로 만들 수 있다.

  1. 스크린샷 캡쳐: html2canvas 라이브러리를 사용하여 원본 엘리먼트의 스크린샷을 캡처한다.
  2. 다수의 레이어로 분할: 원본과 동일한 크기의 이미지 데이터 인스턴스를 생성하고, 원본 스크린샷의 각 픽셀의 데이터를 읽어서 임의의 이미지 데이터 인스턴스로 옮긴다. 픽셀 데이터가 반영된 이후에 이를 각 캔버스에 반영한다.
  3. 각 레이어를 회전하고 이동: 각 캔버스 레이어를 임의의 각도로 회전시키고, 불투명도를 조정하여 점차 보이지 않게 한다. 이 과정에서 각 레이어에 효과를 순차적으로 주면 특정 방향으로 이동하는 효과를 볼 수 있다.

코드와 함께 예제를 단계별로 자세히 살펴봅시다.

1. 스크린샷 캡쳐

오른쪽의 예제는 다음과 같은 HTML 코드로 작성되어있다.

다음 단계의 작업을 위해서 HTML 계층 구조를 가진 엘리먼트를 이미지로 변환하는 과정이 필요한데, html2canvas 라이브러리를 사용하면 특정 HTML 엘리먼트의 스크린샷을 얻을 수 있다.

import html2canvas from 'html2canvas';
html2canvas(element, {
backgroundColor: null,
}).then((canvas) => {
// doSomething(canvas);
});

위의 코드를 실행하여 대상 엘리먼트의 스크린샷을 생성한다. (찰칵)

BLACK WIDOW / NATASHA ROMANOFF

https://www.marvel.com/characters/black-widow-natasha-romanoff

Despite super spy Natasha Romanoff’s checkered past, she’s become one of S.H.I.E.L.D.’s most deadly assassins and a frequent member of the Avengers.

2. 다수의 레이어로 분할

이번 단계에서는 이전 단계에서 얻은 스크린샷의 각 픽셀을 다수의 canvas에 나누어 그린다.

canvas에서는 영역의 픽셀 데이터를 나타내기 위해 ImageData 인터페이스를 사용하는데, 아래의 각 단계에서는 ImageData를 생성하고, 읽고, 옮기는 작업을 수행할 예정이다.

원본과 동일한 크기의 이미지 데이터 인스턴스를 생성한다.

createImageData 함수를 사용하여 원본과 동일한 크기의 빈 ImageData를 정해진 개수만큼 만든다.

const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const layers = new Array(numOfLayers)
.fill(null)
.map(() => ctx.createImageData(width, height));

원본 스크린샷의 각 픽셀의 데이터를 읽어서 임의의 이미지 데이터 인스턴스로 옮긴다.

getImageData 함수로 원본 스크린샷의 ImageData를 가져온 뒤, 모든 픽셀을 순회하면서 각 픽셀의 정보를 임의의 ImageData로 옮긴다.

const imageData = ctx.getImageData(0, 0, width, height);
for (let x = 0; x < width; x += 1) {
for (let y = 0; y < height; y += 1) {
const i = Math.floor(
(numOfLayers * (Math.random() + (2 * x) / width)) / 3,
);
const p = 4 * (y * width + x);
for (let j = 0; j < 4; j += 1) {
layers[i].data[p + j] = imageData.data[p + j];
}
}
}

픽셀 데이터가 반영된 이후에 이를 각 캔버스에 반영한다.

모든 ImageData가 준비되면 canvas를 만들고 해당 정보를 사용하여 그림을 그린다. 각 canvas 레이어에 픽셀이 흩어져 있지만, 겹쳐서 보면 원본과 크게 다르지 않다.

layers.map((layer) => {
const clone = canvas.cloneNode();
clone.getContext('2d').putImageData(layer, 0, 0);
return clone;
});

3. 각 레이어를 회전하고 이동

canvas 레이어가 준비가 되면 css의 transform, opacity를 사용하여 각각의 레이어를 순차적으로 임의의 각도로 회전시키면서 투명하게 만들면 효과가 완성된다.

THE END


Edit on GitHub


개발자를 꿈꾸는 아들을 둔 아빠 개발자입니다.
데이터 시각화에 관심이 있으며, 재미있는 프로그램을 만드는 것을 좋아합니다.