중간계의 프런트엔드

멀티스크린 개발 둘러보기

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

Chrome 실험 중간계 여행 개발에 관한 첫 번째 도움말에서는 휴대기기용 WebGL 개발에 중점을 두었습니다. 이 도움말에서는 나머지 HTML5 프런트엔드를 만들 때 발생한 문제, 문제점, 해결 방법을 설명합니다.

동일한 사이트의 세 가지 버전

먼저 화면 크기와 기기 기능 관점에서 이 실험을 데스크톱 컴퓨터와 휴대기기에서 모두 작동하도록 조정하는 방법을 알아보겠습니다.

전체 프로젝트는 매우 '영화 같은' 스타일을 기반으로 합니다. 디자인 측면에서는 영화의 마법을 유지하기 위해 가로 모드 지향의 고정 프레임 내에 환경을 유지하고자 했습니다. 프로젝트의 상당 부분이 양방향 미니 '게임'으로 구성되어 있으므로 프레임이 오버플로되도록 허용해도 의미가 없습니다.

방문 페이지를 예로 들면 다양한 크기에 맞게 디자인을 조정하는 방법을 알 수 있습니다.

독수리가 방문 페이지로 데려다 줬습니다.
이글스가 방문 페이지로 이동했습니다.

사이트에는 데스크톱, 태블릿, 모바일의 세 가지 모드가 있습니다. 레이아웃을 처리하기 위해서만이 아니라 런타임에 로드된 애셋을 처리하고 다양한 성능 최적화를 추가해야 하기 때문입니다. 데스크톱 컴퓨터 및 노트북보다 해상도가 높지만 휴대전화보다 성능이 낮은 기기의 경우 궁극적인 규칙을 정의하는 것은 쉽지 않습니다.

Google에서는 사용자 에이전트 데이터를 사용하여 휴대기기를 감지하고 뷰포트 크기 테스트를 사용하여 그중 태블릿 (645px 이상)을 타겟팅합니다. 레이아웃이 미디어 쿼리 또는 JavaScript의 상대/비율 배치를 기반으로 하므로 각 모드는 실제로 모든 해상도를 렌더링할 수 있습니다.

이 경우 디자인은 그리드나 규칙을 기반으로 하지 않으며 섹션마다 고유하므로 어떤 브레이크포인트나 스타일을 사용할지는 특정 요소와 시나리오에 따라 다릅니다. 멋진 sass-mixins 및 media-queries로 완벽한 레이아웃을 설정했는데 마우스 위치나 동적 객체를 기반으로 효과를 추가해야 했고 결국 모든 것을 JavaScript로 다시 작성해야 했던 경우가 여러 번 있었습니다.

또한 다음 예와 같이 스타일에서 이 정보를 사용할 수 있도록 head 태그에 현재 모드가 포함된 클래스를 추가합니다 (SCSS).

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

Google에서는 약 360x320까지 모든 크기를 지원하므로 몰입형 웹 환경을 만들 때 상당히 어려운 점이 있었습니다. 데스크톱에서는 가능한 한 더 큰 표시 영역에서 사이트를 경험할 수 있도록 스크롤바가 표시되기 전에 최소 크기가 설정되어 있습니다. 휴대기기에서는 기기를 가로 모드로 전환하라는 메시지가 표시되는 양방향 환경까지 가로 모드와 세로 모드 모두를 허용하기로 결정했습니다. 이에 대한 반론으로 세로 모드에서는 가로 모드만큼 몰입감이 없다는 의견이 있었지만 사이트가 상당히 잘 확장되었기 때문에 유지했습니다.

레이아웃은 입력 유형, 기기 방향, 센서와 같은 기능 감지와 혼동해서는 안 됩니다. 이러한 기능은 모든 모드에 존재할 수 있으며 모든 모드에 걸쳐야 합니다. 마우스와 터치를 동시에 지원하는 것이 그 예입니다. 품질을 위한 Retina 보정이지만 무엇보다도 실적이 중요합니다. 품질이 낮을 때도 더 좋을 수 있습니다. 예를 들어 캔버스는 레티나 디스플레이의 WebGL 환경에서 해상도의 절반이므로 그렇지 않으면 4배의 픽셀 수를 렌더링해야 합니다.

개발 중에는 특히 새로운 개선된 기능과 많은 사전 설정이 있는 Chrome Canary에서 DevTools의 에뮬레이터 도구를 자주 사용했습니다. 디자인을 빠르게 검증하는 데 좋은 방법입니다. 하지만 실제 기기에서 정기적으로 테스트해야 했습니다. 그 중 하나는 사이트가 전체 화면에 맞게 조정되기 때문입니다. 세로 스크롤이 있는 페이지는 대부분의 경우 스크롤할 때 브라우저 UI를 숨깁니다 (현재 iOS7의 Safari에는 이 문제가 있음). 하지만 이와 관계없이 모든 항목을 맞춰야 했습니다. 또한 에뮬레이터에서 사전 설정을 사용하고 화면 크기 설정을 변경하여 사용 가능한 공간 손실을 시뮬레이션했습니다. 메모리 사용량과 성능을 모니터링하는 데도 실제 기기에서 테스트하는 것이 중요합니다.

상태 처리

방문 페이지를 지나면 중간계 지도로 이동합니다. URL이 변경되는 것을 확인했나요? 이 사이트는 History API를 사용하여 라우팅을 처리하는 단일 페이지 애플리케이션입니다.

사이트의 각 섹션은 DOM 요소, 전환, 애셋 로드, 폐기와 같은 기능의 템플릿을 상속받는 자체 객체입니다. 사이트의 여러 부분을 탐색하면 섹션이 시작되고 요소가 DOM에 추가 및 삭제되며 현재 섹션의 애셋이 로드됩니다.

사용자는 언제든지 브라우저의 뒤로 버튼을 누르거나 메뉴를 통해 탐색할 수 있으므로 생성된 모든 항목은 특정 시점에 삭제되어야 합니다. 제한 시간과 애니메이션은 중지되고 삭제되어야 합니다. 그렇지 않으면 원치 않는 동작, 오류, 메모리 누수가 발생합니다. 특히 기한이 다가오고 모든 작업을 최대한 빨리 처리해야 하는 경우에는 쉽지 않은 작업일 수 있습니다.

위치 표시

중간계의 아름다운 배경과 캐릭터를 보여주기 위해 가로로 드래그하거나 스와이프할 수 있는 이미지 및 텍스트 구성요소의 모듈식 시스템을 빌드했습니다. 클립이 재생될 때까지 움직임을 옆으로 중지하는 이미지 시퀀스와 같이 다양한 범위에 서로 다른 속도를 적용하기 위해 여기서는 스크롤바를 사용 설정하지 않았습니다.

트란두일의 홀
Thranduil's Hall 타임라인

타임라인

개발이 시작될 때는 각 위치의 모듈 콘텐츠를 알 수 없었습니다. Google은 모든 것을 6번 다시 빌드하지 않고도 6가지 위치 프레젠테이션을 자유롭게 만들 수 있는, 가로 타임라인에 다양한 유형의 미디어와 정보를 표시하는 템플릿 방식을 원했습니다. 이를 관리하기 위해 설정과 모듈의 동작에 따라 모듈의 화면 이동을 처리하는 타임라인 컨트롤러를 만들었습니다.

모듈 및 동작 구성요소

이미지 시퀀스, 정지 이미지, 시차 장면, 초점 전환 장면, 텍스트를 지원하는 다양한 모듈을 추가했습니다.

시차 장면 모듈에는 정확한 위치의 뷰포트 진행 상황을 리슨하는 맞춤 수의 레이어가 있는 불투명한 배경이 있습니다.

초점 전환 장면은 시차 버킷의 변형으로, 각 레이어에 두 개의 이미지를 사용하고 이 이미지를 페이드 인 및 페이드 아웃하여 초점 변경을 시뮬레이션합니다. 흐리게 처리 필터를 사용해 보았지만 여전히 비용이 너무 많이 들기 때문에 CSS 셰이더가 나올 때까지 기다리겠습니다.

텍스트 모듈의 콘텐츠는 TweenMax 플러그인 Draggable를 사용하여 드래그할 수 있습니다. 스크롤 휠이나 두 손가락 스와이프를 사용하여 수직으로 스크롤할 수도 있습니다. 스와이프하고 놓을 때 플링 스타일 물리학을 추가하는 throw-props-plugin에 유의하세요.

모듈은 구성요소 집합으로 추가되는 다양한 동작을 가질 수도 있습니다. 모두 자체 타겟 선택기와 설정을 갖습니다. Translate: 요소 이동, Scale(확대/축소), 정보 오버레이의 핫스팟, 시각적 테스트를 위한 디버그 측정항목, 시작 제목 오버레이, 플레어 레이어 등을 사용할 수 있습니다. 이러한 요소는 DOM에 추가되거나 모듈 내에서 대상 요소를 제어합니다.

이렇게 하면 로드할 애셋을 정의하고 다양한 종류의 모듈과 구성요소를 설정하는 구성 파일 하나만으로 다양한 위치를 만들 수 있습니다.

이미지 시퀀스

성능 및 다운로드 크기 측면에서 가장 어려운 모듈은 이미지 시퀀스입니다. 이 주제에 관해 읽을 만한 자료가 많습니다. 모바일 및 태블릿에서는 이를 정지 이미지로 대체합니다. 모바일에서 적절한 품질을 얻으려면 디코딩하고 메모리에 저장하기에는 데이터가 너무 많습니다. 먼저 배경 이미지와 스프라이트 시트를 사용하는 여러 대안 솔루션을 시도했지만 GPU가 스프라이트 시트 간에 전환해야 할 때 메모리 문제가 발생하고 지연이 발생했습니다. 그런 다음 img 요소를 전환해 보았지만 속도가 너무 느렸습니다. 스프라이트 시트에서 캔버스로 프레임을 그리는 것이 가장 성능이 좋으므로 이를 최적화하기 시작했습니다. 프레임마다 계산 시간을 절약하기 위해 캔버스에 쓸 이미지 데이터가 임시 캔버스를 통해 사전 처리되고 putImageData()를 사용하여 배열에 저장되며 디코딩되어 사용할 준비가 됩니다. 그러면 원본 스프라이트 시트는 가비지 컬렉션될 수 있으며 메모리에 필요한 최소한의 데이터만 저장됩니다. 디코딩되지 않은 이미지를 저장하는 것이 실제로 더 적을 수도 있지만, 이 방법으로 시퀀스를 스크러빙할 때 성능이 향상됩니다. 프레임은 640x400으로 매우 작지만 스크러빙 중에만 표시됩니다. 멈추면 고해상도 이미지가 로드되고 빠르게 페이드 인됩니다.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

스프라이트 시트는 Imagemagick으로 생성됩니다. 다음은 폴더 내 모든 이미지의 스프라이트 시트를 만드는 방법을 보여주는 간단한 GitHub 예시입니다.

모듈에 애니메이션 적용

타임라인에 모듈을 배치하기 위해 화면 밖에 표시되는 타임라인의 숨겨진 표현은 '플레이헤드'와 타임라인의 너비를 추적합니다. 이는 코드만으로도 가능하지만 개발 및 디버깅 시 시각적 표현이 좋았습니다. 실제로 실행할 때는 크기를 조절할 때만 업데이트되어 크기를 설정합니다. 일부 모듈은 뷰포트를 채우고 일부는 자체 비율을 가지므로 모든 해상도에서 모든 항목이 표시되고 너무 많이 잘리지 않도록 크기를 조절하고 배치하는 것이 약간 까다로웠습니다. 각 모듈에는 두 개의 진행률 표시기가 있습니다. 하나는 화면에 표시되는 위치를 나타내고 하나는 모듈 자체의 길이를 나타냅니다. 시차 이동을 만들 때는 뷰에 있을 때 예상 위치와 동기화할 물체의 시작 위치와 끝 위치를 계산하기가 어려운 경우가 많습니다. 모듈이 뷰에 표시되고 내부 타임라인을 재생하며 다시 화면에서 사라지는 시점을 정확하게 파악하는 것이 좋습니다.

각 모듈에는 불투명도를 조정하는 미묘한 검은색 레이어가 상단에 있어 중앙에 있을 때 완전히 투명해집니다. 이렇게 하면 한 번에 하나의 모듈에 집중하여 환경을 개선할 수 있습니다.

페이지 성능

작동하는 프로토타입에서 버벅거림이 없는 출시 버전으로 이동한다는 것은 브라우저에서 어떤 일이 일어나는지 추측하는 대신 알게 된다는 것을 의미합니다. 이때 Chrome DevTools가 큰 도움이 됩니다.

사이트 최적화에 많은 시간을 할애했습니다. 하드웨어 가속을 강제하는 것은 물론 원활한 애니메이션을 얻기 위한 가장 중요한 도구 중 하나입니다. Chrome DevTools에서 다양한 색상의 열과 빨간색 직사각형을 찾습니다. 이 주제에 관한 유용한 도움말이 많이 있으므로 모두 읽어보세요. 프레임 건너뛰기를 제거하면 즉각적인 보상을 얻을 수 있지만 다시 발생하면 불편함도 즉각적으로 느끼게 됩니다. 그리고 그렇게 될 것입니다. 반복이 필요한 지속적인 프로세스입니다.

트윈 속성, 변환, CSS에는 Greensock의 TweenMax를 사용하는 것이 좋습니다. 컨테이너로 생각하고 새 레이어를 추가할 때 구조를 시각화하세요. 기존 변환은 새 변환으로 덮어쓸 수 있습니다. 2D 값만 트윈하면 CSS 클래스에서 하드웨어 가속을 강제하는 translateZ(0)가 2D 행렬로 대체됩니다. 이 경우 레이어를 가속 모드로 유지하려면 트윈에서 'force3D:true' 속성을 사용하여 2D 행렬 대신 3D 행렬을 만듭니다. CSS와 JavaScript 트윈을 결합하여 스타일을 설정할 때는 잊기 쉽습니다.

필요하지 않은 경우 하드웨어 가속을 강제하지 마세요. 특히 메모리 제약이 더 많은 iOS에서 여러 컨테이너를 하드웨어 가속하려고 하면 GPU 메모리가 빠르게 채워져 원치 않는 결과가 발생할 수 있습니다. 더 작은 애셋을 로드하고 css로 크기를 조절하고 모바일 모드에서 일부 효과를 사용 중지하여 크게 개선되었습니다.

메모리 누수도 기술을 개선해야 하는 분야였습니다. 다양한 WebGL 환경 간에 이동할 때 많은 객체, 재료, 텍스처, 도형이 생성됩니다. 섹션을 탐색하여 삭제할 때 가비지 컬렉션을 실행할 준비가 되지 않은 경우 메모리가 부족해지면 잠시 후 기기가 비정상 종료될 수 있습니다.

실패하는 Dispose 함수로 섹션을 종료합니다.
실패하는 Dispose 함수로 섹션을 종료합니다.
훨씬 낫네요!
훨씬 나아졌습니다.

누수를 찾으려면 DevTools에서 타임라인을 기록하고 힙 스냅샷을 캡처하는 간단한 워크플로를 따르면 됩니다. 필터링할 수 있는 특정 객체(예: 3D 도형 또는 특정 라이브러리)가 있으면 더 쉽습니다. 위 예에서는 3D 장면이 여전히 남아 있고 도형을 저장한 배열이 지워지지 않은 것으로 나타났습니다. 객체가 있는 위치를 찾기 어렵다면 유지 경로라는 유용한 기능을 사용해 보세요. 힙 스냅샷에서 검사하려는 객체를 클릭하면 아래 패널에 정보가 표시됩니다. 작은 객체와 함께 적절한 구조를 사용하면 참조를 찾는 데 도움이 됩니다.

장면이 EffectComposer에서 참조되었습니다.
EffectComposer에서 장면이 참조되었습니다.

일반적으로 DOM을 조작하기 전에 한 번 더 생각하는 것이 좋습니다. 이때 효율성을 고려하세요. 가능하면 게임 루프 내에서 DOM을 조작하지 마세요. 재사용할 수 있도록 참조를 변수에 저장합니다. 요소를 검색해야 하는 경우 전략적 컨테이너에 대한 참조를 저장하고 가장 가까운 조상 요소 내에서 검색하여 최단 경로를 사용하세요.

새로 추가된 요소의 크기를 읽거나 레이아웃 버그가 발생할 때 클래스를 삭제/추가할 때 지연합니다. 또는 레이아웃이 트리거되었는지 확인합니다. 브라우저가 일괄적으로 스타일로 변경되고 다음 레이아웃 트리거 후 업데이트되지 않는 경우가 있습니다. 이 문제는 때로 큰 문제가 될 수 있지만, 이유가 있으므로 백그라운드에서 작동하는 방식을 알아보세요. 큰 도움이 될 것입니다.

전체 화면

가능한 경우 Fullscreen API를 통해 메뉴에서 사이트를 전체 화면 모드로 설정할 수 있습니다. 하지만 기기에서는 브라우저가 전체 화면으로 설정할 수도 있습니다. 이전에는 iOS의 Safari에서 이를 제어할 수 있는 해킹이 있었지만 더 이상 사용할 수 없으므로 스크롤되지 않는 페이지를 만들 때는 이 해킹 없이 작동하도록 디자인을 준비해야 합니다. 이로 인해 많은 웹 앱이 중단되었으므로 향후 업데이트에서 업데이트가 있을 수 있습니다.

애셋

실험에 대한 애니메이션 안내
실험에 관한 애니메이션 안내

사이트 전반에서 다양한 유형의 애셋을 사용합니다. 이미지 (PNG 및 JPEG), SVG (인라인 및 배경), 스프라이트 시트 (PNG), 맞춤 아이콘 글꼴, Adobe Edge 애니메이션을 사용합니다. 요소를 벡터 기반으로 할 수 없는 확장 소재 및 애니메이션 (스프라이트 시트)에는 PNG를 사용하고, 그 외에는 최대한 SVG를 사용합니다.

벡터 형식은 크기를 조정해도 품질이 저하되지 않습니다. 모든 기기의 경우 파일 1개

  • 파일 크기가 작습니다.
  • 각 부분을 개별적으로 애니메이션할 수 있습니다 (고급 애니메이션에 적합). 예를 들어 호빗 로고의 '부제'(스마우그의 폐허)가 축소될 때 숨깁니다.
  • SVG HTML 태그로 삽입하거나 추가 로드 없이 background-image로 사용할 수 있습니다 (html 페이지와 동시에 로드됨).

아이콘 서체는 확장성과 관련하여 SVG와 동일한 이점을 가지며, 색상 (마우스 오버, 활성 상태 등)만 변경하면 되는 아이콘과 같은 작은 요소에는 SVG 대신 사용됩니다. 아이콘은 재사용하기도 매우 쉽습니다. 요소의 CSS 'content' 속성을 설정하기만 하면 됩니다.

애니메이션

코드로 SVG 요소에 애니메이션을 적용하는 데 시간이 많이 걸리는 경우가 있습니다. 특히 디자인 과정에서 애니메이션을 많이 변경해야 하는 경우 그렇습니다. 디자이너와 개발자 간의 워크플로를 개선하기 위해 일부 애니메이션 (게임 전 안내)에는 Adobe Edge를 사용합니다. 애니메이션 워크플로는 Flash와 매우 유사하여 팀에 도움이 되었지만, 자체 로더와 구현 로직이 포함되어 있어 애셋 로드 프로세스에 Edge 애니메이션을 통합할 때 몇 가지 단점이 있습니다.

웹에서 애셋과 수제 애니메이션을 처리하는 완벽한 워크플로를 갖추기까지는 아직 갈 길이 멀다고 생각합니다. Edge와 같은 도구가 어떻게 발전할지 기대됩니다. 다른 애니메이션 도구 및 워크플로에 관한 제안사항이 있으면 댓글로 알려주세요.

결론

이제 프로젝트의 모든 부분이 출시되고 최종 결과를 살펴본 결과, 최신 모바일 브라우저의 상태에 깊은 인상을 받았습니다. 이 프로젝트를 시작할 때는 얼마나 원활하게 통합하고 성능을 개선할 수 있을지 기대치가 낮았습니다. 이번 작업은 Google에 큰 학습 경험이 되었으며 반복과 테스트에 많은 시간을 할애하면서 최신 브라우저의 작동 방식에 대한 이해가 향상되었습니다. 이러한 유형의 프로젝트에서 제작 시간을 단축하려면 추측에서 앎으로 전환해야 합니다.