모바일 성능 최적화를 위한 HTML5 기술

Wesley Hales
Wesley Hales

소개

회전하는 새로고침, 끊기는 페이지 전환, 탭 이벤트의 주기적 지연은 오늘날 모바일 웹 환경에서 발생하는 몇 가지 문제 중 하나일 뿐입니다. 개발자는 네이티브에 최대한 근접하려고 하지만 해킹, 재설정, 경직된 프레임워크로 인해 방해를 받는 경우가 많습니다.

이 도움말에서는 모바일 HTML5 웹 앱을 만드는 데 필요한 최소한의 사항을 설명합니다. 주요 내용은 오늘날의 모바일 프레임워크가 숨기려고 하는 숨겨진 복잡성을 파악하는 것입니다. 핵심 HTML5 API를 사용하는 최소한의 접근 방식과 자체 프레임워크를 작성하거나 현재 사용 중인 프레임워크에 기여할 수 있는 기본적인 기초를 확인할 수 있습니다.

하드웨어 가속

일반적으로 GPU는 세부적인 3D 모델링이나 CAD 다이어그램을 처리하지만, 이 경우에는 GPU를 통해 원시 도형(div, 배경, 음영이 적용된 텍스트, 이미지 등)이 부드럽게 표시되고 부드럽게 애니메이션되기를 바랍니다. 안타까운 점은 대부분의 프런트엔드 개발자가 시맨틱스를 고려하지 않고 이 애니메이션 프로세스를 서드 파티 프레임워크에 전달하고 있다는 것입니다. 이러한 핵심 CSS3 기능을 마스킹해야 할까요? 이러한 사항에 관심을 가져야 하는 몇 가지 이유를 알려드리겠습니다.

  1. 메모리 할당 및 계산 부담: 하드웨어 가속화를 위해 DOM의 모든 요소를 컴포지팅하면 다음에 코드를 수정하는 사람이 나를 쫓아와 심하게 때릴 수 있습니다.

  2. 전원 소모량: 하드웨어가 작동하면 배터리도 작동합니다. 모바일용으로 개발할 때 개발자는 모바일 웹 앱을 작성할 때 다양한 기기 제약조건을 고려해야 합니다. 브라우저 제조업체가 점점 더 많은 기기 하드웨어에 대한 액세스를 지원하기 시작하면 이러한 동향은 더욱 확산될 것입니다.

  3. 충돌: 이미 가속된 페이지의 일부에 하드웨어 가속을 적용할 때 글리치 동작이 발생했습니다. 따라서 중복 가속이 있는지 여부를 아는 것이 매우 중요합니다.

사용자 상호작용이 원활하고 최대한 네이티브에 가까워지도록 하려면 브라우저가 제대로 작동하도록 해야 합니다. 모바일 기기 CPU가 초기 애니메이션을 설정한 다음 GPU가 애니메이션 프로세스 중에 여러 레이어를 합성하는 작업만 담당하는 것이 이상적입니다. translate3d, scale3d, translateZ는 애니메이션된 요소에 자체 레이어를 제공하여 기기가 모든 항목을 원활하게 렌더링할 수 있도록 합니다. 가속된 합성 및 WebKit 작동 방식에 대해 자세히 알아보려면 Ariya Hidayat의 블로그에서 유용한 정보를 확인하세요.

페이지 전환

모바일 웹 앱을 개발할 때 가장 일반적인 세 가지 사용자 상호작용 접근 방식인 슬라이드, 플립, 회전 효과를 살펴보겠습니다.

이 코드가 실행되는 모습을 여기 (http://slidfast.appspot.com/slide-flip-rotate.html)에서 확인할 수 있습니다. 참고: 이 데모는 휴대기기용으로 빌드되었으므로 에뮬레이터를 실행하거나 휴대전화나 태블릿을 사용하거나 브라우저 창 크기를 1024px 이하로 줄이세요.

먼저 슬라이드, 플립, 회전 전환과 이러한 전환이 가속되는 방식을 살펴봅니다. 각 애니메이션에 CSS와 JavaScript가 3~4줄만 사용되는 것을 볼 수 있습니다.

슬라이딩

세 가지 전환 방식 중 가장 일반적인 슬라이딩 페이지 전환은 모바일 애플리케이션의 네이티브 느낌을 모방합니다. 슬라이드 전환이 호출되어 새 콘텐츠 영역을 뷰 포트로 가져옵니다.

슬라이드 효과의 경우 먼저 마크업을 선언합니다.

<div id="home-page" class="page">
  <h1>Home Page</h1>
</div>

<div id="products-page" class="page stage-right">
  <h1>Products Page</h1>
</div>

<div id="about-page" class="page stage-left">
  <h1>About Page</h1>
</div>

페이지 스테이징을 왼쪽 또는 오른쪽으로 하는 개념을 확인해 보세요. 기본적으로 어떤 방향이든 가능하지만 이 방향이 가장 일반적입니다.

이제 CSS 몇 줄로 애니메이션과 하드웨어 가속을 모두 사용할 수 있습니다. 실제 애니메이션은 페이지 div 요소에서 클래스를 바꿀 때 발생합니다.

.page {
  position: absolute;
  width: 100%;
  height: 100%;
  /*activate the GPU for compositing each page */
  -webkit-transform: translate3d(0, 0, 0);
}

translate3d(0,0,0)는 '만병통치약' 접근 방식으로 알려져 있습니다.

사용자가 탐색 요소를 클릭하면 다음 JavaScript를 실행하여 클래스를 전환합니다. 서드 파티 프레임워크는 사용되지 않으며 순수한 JavaScript입니다. ;)

function getElement(id) {
  return document.getElementById(id);
}

function slideTo(id) {
  //1.) the page we are bringing into focus dictates how
  // the current page will exit. So let's see what classes
  // our incoming page is using. We know it will have stage[right|left|etc...]
  var classes = getElement(id).className.split(' ');

  //2.) decide if the incoming page is assigned to right or left
  // (-1 if no match)
  var stageType = classes.indexOf('stage-left');

  //3.) on initial page load focusPage is null, so we need
  // to set the default page which we're currently seeing.
  if (FOCUS_PAGE == null) {
    // use home page
    FOCUS_PAGE = getElement('home-page');
  }

  //4.) decide how this focused page should exit.
  if (stageType > 0) {
    FOCUS_PAGE.className = 'page transition stage-right';
  } else {
    FOCUS_PAGE.className = 'page transition stage-left';
  }

  //5. refresh/set the global variable
  FOCUS_PAGE = getElement(id);

  //6. Bring in the new page.
  FOCUS_PAGE.className = 'page transition stage-center';
}

stage-left 또는 stage-rightstage-center이 되고 페이지가 중앙 뷰 포트로 슬라이드됩니다. CSS3에 완전히 의존하여 무거운 작업을 처리합니다.

.stage-left {
  left: -480px;
}

.stage-right {
  left: 480px;
}

.stage-center {
  top: 0;
  left: 0;
}

다음으로 휴대기기 감지 및 방향을 처리하는 CSS를 살펴보겠습니다. 모든 기기와 모든 해상도를 처리할 수 있습니다 (미디어 쿼리 해상도 참고). 이 데모에서는 휴대기기의 대부분의 세로 모드 및 가로 모드 뷰를 다루기 위해 몇 가지 간단한 예시만 사용했습니다. 이는 기기별로 하드웨어 가속을 적용하는 데도 유용합니다. 예를 들어 WebKit의 데스크톱 버전은 2D인지 3D인지와 관계없이 모든 변환된 요소를 가속하므로 미디어 쿼리를 만들고 해당 수준에서 가속을 제외하는 것이 좋습니다. 하드웨어 가속 트릭은 Android Froyo 2.2 이상에서 속도 개선을 제공하지 않습니다. 모든 컴포지션은 소프트웨어 내에서 실행됩니다.

/* iOS/android phone landscape screen width*/
@media screen and (max-device-width: 480px) and (orientation:landscape) {
  .stage-left {
    left: -480px;
  }

  .stage-right {
    left: 480px;
  }

  .page {
    width: 480px;
  }
}

전환

휴대기기에서 페이지를 스와이프하여 페이지를 닫는 것을 플립이라고 합니다. 여기서는 몇 가지 간단한 JavaScript를 사용하여 iOS 및 Android (WebKit 기반) 기기에서 이 이벤트를 처리합니다.

http://slidfast.appspot.com/slide-flip-rotate.html에서 작동하는 모습을 확인하세요.

터치 이벤트와 전환을 처리할 때는 먼저 요소의 현재 위치를 처리해야 합니다. WebKitCSSMatrix에 관한 자세한 내용은 이 문서를 참고하세요.

function pageMove(event) {
  // get position after transform
  var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
  var pagePosition = curTransform.m41;
}

페이지 전환에 CSS3 이즈아웃 전환을 사용하고 있으므로 일반적인 element.offsetLeft는 작동하지 않습니다.

다음으로 사용자가 뒤집는 방향을 파악하고 이벤트 (페이지 탐색)가 발생할 기준점을 설정하려고 합니다.

if (pagePosition >= 0) {
 //moving current page to the right
 //so means we're flipping backwards
   if ((pagePosition > pageFlipThreshold) || (swipeTime < swipeThreshold)) {
     //user wants to go backward
     slideDirection = 'right';
   } else {
     slideDirection = null;
   }
} else {
  //current page is sliding to the left
  if ((swipeTime < swipeThreshold) || (pagePosition < pageFlipThreshold)) {
    //user wants to go forward
    slideDirection = 'left';
  } else {
    slideDirection = null;
  }
}

swipeTime도 밀리초 단위로 측정하고 있습니다. 이렇게 하면 사용자가 화면을 빠르게 스와이프하여 페이지를 넘길 때 탐색 이벤트가 실행됩니다.

손가락이 화면을 터치하는 동안 페이지의 위치를 지정하고 애니메이션을 네이티브로 표시하기 위해 각 이벤트가 실행된 후 CSS3 전환을 사용합니다.

function positionPage(end) {
  page.style.webkitTransform = 'translate3d('+ currentPos + 'px, 0, 0)';
  if (end) {
    page.style.WebkitTransition = 'all .4s ease-out';
    //page.style.WebkitTransition = 'all .4s cubic-bezier(0,.58,.58,1)'
  } else {
    page.style.WebkitTransition = 'all .2s ease-out';
  }
  page.style.WebkitUserSelect = 'none';
}

저는 큐빅 베지어를 사용해 전환에 최상의 네이티브 느낌을 부여하려고 했지만 ease-out이 효과가 있었습니다.

마지막으로 탐색을 실행하려면 이전 데모에서 사용한 이전에 정의된 slideTo() 메서드를 호출해야 합니다.

track.ontouchend = function(event) {
  pageMove(event);
  if (slideDirection == 'left') {
    slideTo('products-page');
  } else if (slideDirection == 'right') {
    slideTo('home-page');
  }
}

회전

다음으로 이 데모에서 사용되는 회전 애니메이션을 살펴보겠습니다. 언제든지 '연락처' 메뉴 옵션을 탭하여 현재 보고 있는 페이지를 180도 회전하여 뒷면을 표시할 수 있습니다. 전환 클래스 onclick를 할당하는 데 CSS 몇 줄과 JavaScript가 조금만 있으면 됩니다. 참고: 회전 전환은 3D CSS 변환 기능이 없으므로 대부분의 Android 버전에서 올바르게 렌더링되지 않습니다. 안타깝게도 Android는 뒤집기를 무시하는 대신 뒤집는 대신 회전하여 페이지를 '수레 바퀴'에서 떨어뜨립니다. 지원이 개선될 때까지 이 전환을 가끔 사용하는 것이 좋습니다.

마크업 (앞면과 뒷면의 기본 개념):

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
    <div id="contact-page" class="page">
        <h1>Contact Page</h1>
    </div>
</div>

JavaScript:

function flip(id) {
  // get a handle on the flippable region
  var front = getElement('front');
  var back = getElement('back');

  // again, just a simple way to see what the state is
  var classes = front.className.split(' ');
  var flipped = classes.indexOf('flipped');

  if (flipped >= 0) {
    // already flipped, so return to original
    front.className = 'normal';
    back.className = 'flipped';
    FLIPPED = false;
  } else {
    // do the flip
    front.className = 'flipped';
    back.className = 'normal';
    FLIPPED = true;
  }
}

CSS:

/*----------------------------flip transition */
#back,
#front {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  -webkit-transition-duration: .5s;
  -webkit-transform-style: preserve-3d;
}

.normal {
  -webkit-transform: rotateY(0deg);
}

.flipped {
  -webkit-user-select: element;
  -webkit-transform: rotateY(180deg);
}

하드웨어 가속 디버깅

기본 전환을 살펴봤으니 이제 전환 메커니즘의 작동 방식과 구성 방식을 살펴보겠습니다.

이 마법 같은 디버깅 세션을 실행할 수 있도록 몇 가지 브라우저와 개발자가 선택한 IDE를 실행해 보겠습니다. 먼저 명령줄에서 Safari를 시작하여 일부 디버깅 환경 변수를 활용합니다. 저는 Mac을 사용하고 있으므로 OS에 따라 명령어가 다를 수 있습니다. 터미널을 열고 다음을 입력합니다.

  • $> export CA_COLOR_OPAQUE=1
  • $> export CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

그러면 몇 가지 디버깅 도우미와 함께 Safari가 시작됩니다. CA_COLOR_OPAQUE는 실제로 합성되거나 가속되는 요소를 보여줍니다. CA_LOG_MEMORY_USAGE는 그리기 작업을 백업 저장소로 전송할 때 사용 중인 메모리 양을 보여줍니다. 이를 통해 휴대기기에 얼마나 많은 부하가 가해지고 있는지 정확하게 파악할 수 있으며 GPU 사용량이 타겟 기기의 배터리를 소모하는 방식에 대한 힌트를 얻을 수도 있습니다.

이제 Chrome을 실행하여 적절한 초당 프레임 수(FPS) 정보를 확인해 보겠습니다.

  1. Chrome 웹브라우저를 엽니다.
  2. URL 표시줄에 about:flags를 입력합니다.
  3. 몇 가지 항목을 아래로 스크롤하고 FPS 카운터의 '사용 설정'을 클릭합니다.

업그레이드된 Chrome 버전에서 이 페이지를 보면 왼쪽 상단에 빨간색 FPS 카운터가 표시됩니다.

Chrome FPS

이렇게 하면 하드웨어 가속이 사용 설정되었는지 알 수 있습니다. 또한 애니메이션이 실행되는 방식과 누수가 있는지 (중지해야 하는 연속 실행 애니메이션)를 알 수 있습니다.

하드웨어 가속을 실제로 시각화하는 또 다른 방법은 Safari에서 동일한 페이지를 여는 것입니다 (위에서 언급한 환경 변수 사용). 가속된 모든 DOM 요소에는 빨간색 색조가 적용됩니다. 이렇게 하면 레이어별로 컴포지션되는 항목을 정확하게 확인할 수 있습니다. 흰색 탐색은 가속되지 않아 빨간색이 아닙니다.

합성 연락처

Chrome의 유사한 설정은 about:flags의 '합성 렌더링 레이어 테두리'에서도 사용할 수 있습니다.

합성된 레이어를 확인하는 또 다른 좋은 방법은 이 mod가 적용되는 동안 WebKit 낙엽 데모를 보는 것입니다.

낙엽

마지막으로 애플리케이션의 그래픽 하드웨어 성능을 제대로 이해하려면 메모리가 소비되는 방식을 살펴보겠습니다. 여기에서 Mac OS의 CoreAnimation 버퍼에 1.38MB의 그리기 안내를 푸시하고 있는 것을 확인할 수 있습니다. Core Animation 메모리 버퍼는 OpenGL ES와 GPU 간에 공유되어 화면에 표시되는 최종 픽셀을 만듭니다.

Coreanimation 1

브라우저 창의 크기를 조절하거나 최대화하면 메모리도 확장됩니다.

Coreanimation 2

이를 통해 브라우저 크기를 올바른 크기로 조정하는 경우에만 휴대기기에서 메모리가 어떻게 사용되는지 알 수 있습니다. iPhone 환경을 디버그하거나 테스트하는 경우 크기를 480x320px로 조정합니다. 이제 하드웨어 가속이 작동하는 방식과 디버그하는 데 필요한 사항을 정확하게 이해했습니다. 이에 대해 읽어보는 것도 중요하지만, 실제로 GPU 메모리 버퍼가 시각적으로 작동하는 것을 보면 실제로 상황을 바라볼 수 있습니다.

비하인드 스토리: 가져오기 및 캐싱

이제 페이지 및 리소스 캐싱을 한 단계 업그레이드할 차례입니다. JQuery Mobile 및 유사한 프레임워크에서 사용하는 접근 방식과 마찬가지로 동시 AJAX 호출을 사용하여 페이지를 미리 가져와 캐시합니다.

몇 가지 핵심적인 모바일 웹 문제와 이를 수행해야 하는 이유를 살펴보겠습니다.

  • 가져오기: 페이지를 미리 가져오면 사용자가 앱을 오프라인으로 전환할 수 있으며 탐색 작업 간에 대기할 필요가 없습니다. 물론, 기기가 온라인 상태가 될 때 기기의 대역폭이 초로되는 것을 원치 않으므로, 이 기능을 드물게 사용해야 합니다.
  • 캐싱: 다음으로 이러한 페이지를 가져오고 캐시할 때 동시 실행 또는 비동기 접근 방식을 사용합니다. 또한 localStorage를 사용해야 하는데, 이는 기기에서 잘 지원되기 때문입니다. 안타깝게도 비동기식은 아닙니다.
  • AJAX 및 응답 파싱: innerHTML()을 사용하여 AJAX 응답을 DOM에 삽입하는 것은 위험하며 신뢰할 수 없음? 대신 AJAX 응답 삽입 및 동시 호출 처리에 안정적인 메커니즘을 사용합니다. 또한 xhr.responseText 파싱을 위해 HTML5의 몇 가지 새로운 기능을 활용합니다.

슬라이드, 플립, 회전 데모의 코드를 기반으로 시작하여 몇 가지 보조 페이지를 추가하고 연결합니다. 그런 다음 링크를 파싱하고 즉시 전환을 만듭니다.

iPhone 홈

가져오기 및 캐시 데모 보기

보시다시피 여기서는 시맨틱 마크업을 활용하고 있습니다. 다른 페이지로 연결되는 링크입니다. 하위 페이지는 상위 페이지와 동일한 노드/클래스 구조를 따릅니다. 여기서 한 단계 더 나아가 '페이지' 노드 등에 data-* 속성을 사용할 수 있습니다. 그런 다음 별도의 html 파일(/demo2/home-detail.html)에 있는 세부정보 페이지(하위 요소)가 앱 로드 시 전환되도록 로드, 캐시되고 설정됩니다.

<div id="home-page" class="page">
  <h1>Home Page</h1>
  <a href="demo2/home-detail.html" class="fetch">Find out more about the home page!</a>
</div>

이제 JavaScript를 살펴보겠습니다. 편의상 코드에서 도우미나 최적화를 제외했습니다. 여기서는 지정된 DOM 노드 배열을 반복하여 가져오고 캐시할 링크를 파악합니다. 참고: 이 데모에서는 페이지 로드 시 이 메서드 fetchAndCache()가 호출됩니다. 다음 섹션에서 네트워크 연결을 감지하고 호출해야 하는 시점을 결정할 때 다시 작성합니다.

var fetchAndCache = function() {
  // iterate through all nodes in this DOM to find all mobile pages we care about
  var pages = document.getElementsByClassName('page');

  for (var i = 0; i < pages.length; i++) {
    // find all links
    var pageLinks = pages[i].getElementsByTagName('a');

    for (var j = 0; j < pageLinks.length; j++) {
      var link = pageLinks[j];

      if (link.hasAttribute('href') &amp;&amp;
      //'#' in the href tells us that this page is already loaded in the DOM - and
      // that it links to a mobile transition/page
         !(/[\#]/g).test(link.href) &amp;&amp;
        //check for an explicit class name setting to fetch this link
        (link.className.indexOf('fetch') >= 0))  {
         //fetch each url concurrently
         var ai = new ajax(link,function(text,url){
              //insert the new mobile page into the DOM
             insertPages(text,url);
         });
         ai.doGet();
      }
    }
  }
};

Google은 'AJAX' 객체를 사용하여 적절한 비동기 후처리를 보장합니다. AJAX 호출 내에서 localStorage를 사용하는 방법에 관한 고급 설명은 오프라인에서 HTML5로 작업하기를 참고하세요. 이 예에서는 각 요청에 캐싱을 기본적으로 사용하고 서버가 성공(200) 응답이 아닌 다른 응답을 반환할 때 캐시된 객체를 제공하는 것을 보여줍니다.

function processRequest () {
  if (req.readyState == 4) {
    if (req.status == 200) {
      if (supports_local_storage()) {
        localStorage[url] = req.responseText;
      }
      if (callback) callback(req.responseText,url);
    } else {
      // There is an error of some kind, use our cached copy (if available).
      if (!!localStorage[url]) {
        // We have some data cached, return that to the callback.
        callback(localStorage[url],url);
        return;
      }
    }
  }
}

안타깝게도 localStorage는 문자 인코딩에 UTF-16을 사용하므로 각 단일 바이트가 2바이트로 저장되어 저장용량 제한이 5MB에서 총 2.6MB로 줄어듭니다. 이러한 페이지/마크업을 애플리케이션 캐시 범위 외부에서 가져오고 캐시하는 이유 전체는 다음 섹션에서 설명합니다.

HTML5의 iframe 요소가 최근에 개선되어 이제 AJAX 호출에서 반환된 responseText를 파싱하는 간단하고 효과적인 방법이 있습니다. 스크립트 태그 등을 제거하는 3,000줄의 자바스크립트 파서와 정규 표현식이 많습니다. 하지만 브라우저가 가장 잘하는 일을 하도록 놔두면 안 될까요? 이 예에서는 responseText를 임시 숨겨진 iframe에 작성합니다. Google에서는 스크립트를 사용 중지하고 다양한 보안 기능을 제공하는 HTML5 '샌드박스' 속성을 사용하고 있습니다.

사양: 지정된 경우 샌드박스 속성은 iframe에서 호스팅하는 모든 콘텐츠에 추가 제한사항을 사용 설정합니다. 값은 ASCII 대소문자를 구분하지 않는 공백으로 구분된 고유한 토큰의 순서가 지정되지 않은 집합이어야 합니다. 허용되는 값은 allow-forms, allow-same-origin, allow-scripts, allow-top-navigation입니다. 이 속성이 설정되면 콘텐츠가 고유한 출처에서 가져온 것으로 간주되고 양식과 스크립트가 사용 중지되며 링크가 다른 탐색 컨텍스트를 타겟팅하지 못하도록 차단되고 플러그인이 사용 중지됩니다.

var insertPages = function(text, originalLink) {
  var frame = getFrame();
  //write the ajax response text to the frame and let
  //the browser do the work
  frame.write(text);

  //now we have a DOM to work with
  var incomingPages = frame.getElementsByClassName('page');

  var pageCount = incomingPages.length;
  for (var i = 0; i < pageCount; i++) {
    //the new page will always be at index 0 because
    //the last one just got popped off the stack with appendChild (below)
    var newPage = incomingPages[0];

    //stage the new pages to the left by default
    newPage.className = 'page stage-left';

    //find out where to insert
    var location = newPage.parentNode.id == 'back' ? 'back' : 'front';

    try {
      // mobile safari will not allow nodes to be transferred from one DOM to another so
      // we must use adoptNode()
      document.getElementById(location).appendChild(document.adoptNode(newPage));
    } catch(e) {
      // todo graceful degradation?
    }
  }
};

Safari는 한 문서에서 다른 문서로 노드를 암시적으로 이동하는 것을 올바르게 거부합니다. 새 하위 노드가 다른 문서에서 생성된 경우 오류가 발생합니다. 여기서는 adoptNode를 사용하고 모든 것이 잘 작동합니다.

그렇다면 왜 iframe일까요? innerHTML을 사용하면 안 되나요? innerHTML이 이제 HTML5 사양에 포함되지만 서버의 응답을 확인되지 않은 영역으로 삽입 (악의 또는 악)하는 것은 위험한 행위입니다. 이 도움말을 작성하는 동안에는 innerHTML 이외의 항목을 사용하는 아무도 찾을 수 없었습니다. JQuery는 예외가 발생할 때만 추가 대체를 사용하여 핵심에서 이를 사용합니다. JQuery Mobile에서도 이를 사용합니다. 하지만 innerHTML '무작위로 작동이 중지됨'과 관련하여 심층 테스트를 진행하지는 않았지만, 이 문제가 영향을 미치는 모든 플랫폼을 확인하는 것은 매우 흥미로울 것입니다. 어느 접근 방식이 더 성능이 우수한지도 궁금합니다. 이 문제에 관해 양측의 주장을 모두 들었으니까요.

네트워크 유형 감지, 처리 및 프로파일링

이제 웹 앱을 버퍼링(또는 예측 캐시)할 수 있으므로 앱을 더 스마트하게 만드는 적절한 연결 감지 기능을 제공해야 합니다. 이때 모바일 앱 개발은 온라인/오프라인 모드와 연결 속도에 매우 민감해집니다. Network Information API를 입력합니다. 프레젠테이션에서 이 기능을 보여줄 때마다 참석자 중 누군가 손을 들고 '이 기능을 어디에 사용할 수 있나요?'라고 묻습니다. 다음은 매우 스마트한 모바일 웹 앱을 설정하는 방법입니다.

먼저 지루한 상식적인 시나리오를 살펴보겠습니다. 고속열차에서 휴대기기로 웹과 상호작용하는 동안 다양한 시점에 네트워크가 연결 해제될 수 있으며, 지역에 따라 전송 속도가 다를 수 있습니다 (예: 일부 도시 지역에서는 HSPA 또는 3G를 사용할 수 있지만, 외딴 지역에서는 훨씬 느린 2G 기술을 지원할 수 있습니다. 다음 코드는 대부분의 연결 시나리오를 처리합니다.

다음 코드는 다음을 제공합니다.

  • applicationCache를 통한 오프라인 액세스
  • 북마크가 추가되어 있고 오프라인 상태인지 감지합니다.
  • 오프라인에서 온라인으로 전환하거나 그 반대로 전환할 때 이를 감지합니다.
  • 느린 연결을 감지하고 네트워크 유형에 따라 콘텐츠를 가져옵니다.

다시 한번 말씀드리지만 이러한 모든 기능에는 코드가 거의 필요하지 않습니다. 먼저 이벤트와 로드 시나리오를 감지합니다.

window.addEventListener('load', function(e) {
 if (navigator.onLine) {
  // new page load
  processOnline();
 } else {
   // the app is probably already cached and (maybe) bookmarked...
   processOffline();
 }
}, false);

window.addEventListener("offline", function(e) {
  // we just lost our connection and entered offline mode, disable eternal link
  processOffline(e.type);
}, false);

window.addEventListener("online", function(e) {
  // just came back online, enable links
  processOnline(e.type);
}, false);

위의 EventListeners에서 코드가 이벤트 또는 실제 페이지 요청이나 새로고침에서 호출되는지 여부를 코드에 알려야 합니다. 주된 이유는 온라인 모드와 오프라인 모드 간에 전환할 때 body onload 이벤트가 실행되지 않기 때문입니다.

다음으로 ononline 또는 onload 이벤트를 간단하게 확인합니다. 이 코드는 오프라인에서 온라인으로 전환할 때 사용 중지된 링크를 재설정하지만, 이 앱이 더 정교하다면 콘텐츠 가져오기를 재개하거나 간헐적인 연결의 UX를 처리하는 로직을 삽입할 수 있습니다.

function processOnline(eventType) {

  setupApp();
  checkAppCache();

  // reset our once disabled offline links
  if (eventType) {
    for (var i = 0; i < disabledLinks.length; i++) {
      disabledLinks[i].onclick = null;
    }
  }
}

processOffline()도 마찬가지입니다. 여기에서 오프라인 모드용 앱을 조작하고 백그라운드에서 진행 중인 모든 거래를 복구해 보세요. 아래 코드는 모든 외부 링크를 파악하여 사용 중지합니다. 사용자를 오프라인 앱에 영원히 가두는 것입니다. 후후후후!

function processOffline() {
  setupApp();

  // disable external links until we come back - setting the bounds of app
  disabledLinks = getUnconvertedLinks(document);

  // helper for onlcick below
  var onclickHelper = function(e) {
    return function(f) {
      alert('This app is currently offline and cannot access the hotness');return false;
    }
  };

  for (var i = 0; i < disabledLinks.length; i++) {
    if (disabledLinks[i].onclick == null) {
      //alert user we're not online
      disabledLinks[i].onclick = onclickHelper(disabledLinks[i].href);

    }
  }
}

그럼, 좋은 이야기로 넘어가 보겠습니다. 이제 앱이 어떤 연결 상태인지 알 수 있으므로 온라인 상태일 때 연결 유형을 확인하고 적절하게 조정할 수도 있습니다. 각 연결의 댓글에 일반적인 북미 제공업체의 다운로드 속도와 지연 시간을 나열했습니다.

function setupApp(){
  // create a custom object if navigator.connection isn't available
  var connection = navigator.connection || {'type':'0'};
  if (connection.type == 2 || connection.type == 1) {
      //wifi/ethernet
      //Coffee Wifi latency: ~75ms-200ms
      //Home Wifi latency: ~25-35ms
      //Coffee Wifi DL speed: ~550kbps-650kbps
      //Home Wifi DL speed: ~1000kbps-2000kbps
      fetchAndCache(true);
  } else if (connection.type == 3) {
  //edge
      //ATT Edge latency: ~400-600ms
      //ATT Edge DL speed: ~2-10kbps
      fetchAndCache(false);
  } else if (connection.type == 2) {
      //3g
      //ATT 3G latency: ~400ms
      //Verizon 3G latency: ~150-250ms
      //ATT 3G DL speed: ~60-100kbps
      //Verizon 3G DL speed: ~20-70kbps
      fetchAndCache(false);
  } else {
  //unknown
      fetchAndCache(true);
  }
}

fetchAndCache 프로세스를 조정하는 방법은 여러 가지가 있지만 여기서는 특정 연결에 대해 리소스를 비동기(true) 또는 동기(false)로 가져오도록 지시했습니다.

Edge(동기식) 요청 타임라인

Edge 동기화

Wi-Fi(비동기) 요청 일정

WIFI 비동기

이를 통해 느린 연결이나 빠른 연결에 따라 적어도 몇 가지 사용자 환경 조정 방법이 허용됩니다. 그렇다고 해서 만병통치약이 아닌 것은 아닙니다. 또 다른 할 일은 앱이 백그라운드에서 해당 링크의 페이지를 가져오는 동안(연결 속도가 느린 경우) 링크를 클릭할 때 로드 모달을 표시하는 것입니다. 여기서 가장 중요한 점은 최신 HTML5가 제공하는 뛰어난 최신 기능을 통해 사용자 연결의 기능을 최대한 활용하면서 지연 시간을 줄이는 것입니다. 여기에서 네트워크 감지 데모 보기

결론

모바일 HTML5 앱의 여정은 이제 시작에 불과합니다. 이제 HTML5와 이를 지원하는 기술을 중심으로 빌드된 모바일 '프레임워크'의 매우 간단하고 기본적인 기반을 확인할 수 있습니다. 개발자는 래퍼로 가리지 말고 이러한 기능을 핵심적으로 다루고 해결하는 것이 중요하다고 생각합니다.