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

웨슬리 헤일스
웨슬리 헤일스

소개

오늘날의 모바일 웹 환경에서 새로고침 회전, 페이지 전환 끊김, 주기적인 탭 이벤트 지연 등의 문제가 있습니다. 개발자들은 네이티브에 최대한 가깝게 접근하려고 하지만 해킹, 재설정, 경직된 프레임워크로 인해 어려움을 겪는 경우가 많습니다.

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

하드웨어 가속

일반적으로 GPU는 상세한 3D 모델링 또는 CAD 다이어그램을 처리하지만 이 경우에는 원시 그림 (div, 배경, 그림자가 있는 텍스트, 이미지 등)이 GPU를 통해 매끄럽게 표시되고 매끄럽게 애니메이션되기를 원합니다. 안타깝게도 대부분의 프런트엔드 개발자가 시맨틱에 대한 걱정 없이 이 애니메이션 프로세스를 서드 파티 프레임워크로 넘겨가고 있지만, 이러한 핵심 CSS3 기능을 마스킹해야 할까요? 이 부분에 관심을 가지는 것이 중요한 몇 가지 이유를 말씀드리겠습니다.

  1. 메모리 할당 및 계산 부담 — 하드웨어 가속을 위해 DOM의 모든 요소를 합성하는 경우 코드 작업을 하는 다음 사람이 여러분을 따라잡을 수 있습니다.

  2. 전력 소모 — 당연히 하드웨어가 작동하면 배터리도 소모됩니다. 모바일 앱을 개발할 때는 개발자가 모바일 웹 앱을 작성할 때 다양한 기기 제약 조건을 고려해야 합니다. 브라우저 제조업체에서 점점 더 많은 기기 하드웨어에 액세스할 수 있게 됨에 따라 이러한 기술은 더욱 보편화될 것입니다.

  3. 충돌 - 이미 가속된 페이지 부분에 하드웨어 가속을 적용할 때 문제가 발생했습니다. 따라서 가속도가 겹치는지 여부를 아는 것이 매우 중요합니다.

사용자 상호작용을 원활하고 네이티브에 최대한 가깝게 만들려면 브라우저가 작동하게 해야 합니다. 휴대기기 CPU에서 초기 애니메이션을 설정한 다음 GPU가 애니메이션 프로세스 중에 여러 레이어를 합성하도록 하는 것이 이상적입니다. 이것이 Translation3d, scale3d, translateZ가 하는 일입니다. 즉, 애니메이션 요소에 자체 레이어를 제공하므로 기기에서 모든 것을 원활하게 렌더링할 수 있습니다. 빠른 합성 및 WebKit 작동 방식에 대해 자세히 알아보려면 Ariya Hidayat가 자신의 블로그에서 유용한 정보를 많이 찾아볼 수 있습니다.

페이지 전환

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

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

먼저 슬라이드, 뒤집기, 회전 전환과 전환 속도에 대해 분석해 보겠습니다. 각 애니메이션에는 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)는 '묘지(silver 글머리기호)' 접근 방식으로 알려져 있습니다.

사용자가 탐색 요소를 클릭하면 다음 자바스크립트를 실행하여 클래스를 교체합니다. 서드 파티 프레임워크를 사용하지 않습니다. 이는 간단한 자바스크립트입니다. ;)

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 ease-out 전환을 사용하고 있으므로 일반적인 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도 회전하여 뒷면을 확인할 수 있습니다. 이 경우에도 단 몇 줄의 CSS와 일부 JavaScript만으로 전환 클래스 onclick를 할당할 수 있습니다. 참고: 회전 전환은 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>

자바스크립트:

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에 따라 명령어가 다를 수 있습니다. 터미널을 열고 다음을 입력합니다.

  • $> CA_COLOR_OPAQUE=1 내보내기
  • $> 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 넘어지는 잎 데모를 보는 것입니다.

겹쳐진 잎

마지막으로 애플리케이션의 그래픽 하드웨어 성능을 제대로 이해하기 위해 메모리가 어떻게 소모되는지 살펴보겠습니다. 여기서 우리는 1.38MB의 그리기 명령을 Mac OS의 CoreAnimation 버퍼로 푸시하고 있음을 알 수 있습니다. Core Animation 메모리 버퍼는 OpenGL ES와 GPU 간에 공유되어 화면에 표시되는 최종 픽셀을 생성합니다.

코어 애니메이션 1

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

코어 애니메이션 2

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

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

이제 페이지 및 리소스 캐싱을 한 단계 더 발전시킬 차례입니다. JQuery Mobile 및 유사한 프레임워크에서 사용하는 접근 방식과 매우 흡사하게 동시 AJAX 호출로 페이지를 미리 가져오고 캐시합니다.

이제 몇 가지 핵심 모바일 웹 문제와 이 작업이 필요한 이유에 대해 살펴보겠습니다.

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

슬라이드, 뒤집기, 회전 데모의 코드를 기반으로, 먼저 몇 가지 보조 페이지를 추가하고 이 페이지에 연결합니다. 그런 다음 링크를 파싱하고 즉석에서 전환을 생성합니다.

iPhone 홈

가져오기 및 캐시 데모 보기

보시다시피 여기서는 시맨틱 마크업을 활용하고 있습니다. 다른 페이지로 연결되는 링크일 뿐입니다. 하위 페이지는 상위 페이지와 동일한 노드/클래스 구조를 따릅니다. 여기서 한 걸음 더 나아가 'page' 노드 등에 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();
      }
    }
  }
};

'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바이트로 저장되어 Google의 저장용량 한도가 5MB에서 총 2.6MB로 제한됩니다. 애플리케이션 캐시 범위 밖에서 이러한 페이지/마크업을 가져오고 캐시하는 모든 이유는 다음 섹션에서 밝혀집니다.

최근 HTML5 관련 iframe 요소가 발전함에 따라 이제 AJAX 호출에서 반환된 responseText를 간편하고 효과적으로 파싱할 수 있습니다. 스크립트 태그 등을 삭제하는 3,000줄의 자바스크립트 파서와 정규 표현식이 많이 있습니다. 그런데 왜 브라우저가 가장 잘하는 것을 하도록 놔두지 않을까요? 이 예에서는 임시 숨겨진 iframe에 responseText를 씁니다. 저희는 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의 '랜덤 작동 중지'와 관련하여 엄격한 테스트를 실시하지는 않았지만, 영향을 받는 모든 플랫폼을 살펴보면 매우 흥미로울 것입니다. 어떤 접근 방식이 더 효과적인지 알아보는 것도 흥미로울 것입니다. 이에 대한 양측의 주장도 들었습니다.

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

이제 웹 앱을 버퍼링 (또는 예측 캐시)할 수 있으므로 앱을 더욱 스마트하게 만드는 적절한 연결 감지 기능을 제공해야 합니다. 이 경우 모바일 앱 개발은 온라인/오프라인 모드 및 연결 속도에 매우 민감해집니다. The 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);

위의 EventListener에서는 이벤트가 이벤트에서 호출되었는지, 실제 페이지 요청 또는 새로고침에서 호출되는지 코드에 알려야 합니다. 주된 이유는 온라인 모드와 오프라인 모드 간에 전환할 때 본문 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)으로 가져오도록 지시한 것뿐입니다.

에지 (동기) 요청 타임라인

에지 동기화

Wi-Fi (비동기) 요청 타임라인

Wi-Fi 비동기

이를 통해 느리거나 빠른 연결에 따라 사용자 환경을 조정할 수 있습니다. 그렇다고 해서 모든 것을 포괄하는 솔루션은 아닙니다. 또 다른 해결 방법은 앱이 백그라운드에서 링크 페이지를 가져오는 동안 느린 연결에서 링크를 클릭할 때 로드 모달을 표시하는 것입니다. 여기서 중요한 점은 지연 시간을 단축하는 동시에 뛰어난 최신 HTML5의 사용자 연결 기능을 최대한 활용하는 것입니다. 네트워크 감지 데모 보기

결론

모바일 HTML5 앱 개발의 여정은 이제 막 시작되었을 뿐입니다. 이제 HTML5만을 중심으로 구축된 모바일 '프레임워크'의 매우 단순하고 기본적인 토대와 이를 지원하는 기술을 확인할 수 있습니다. 저는 개발자가 래퍼로 가리지 않고 핵심 기능으로 작업하고 해결하는 것이 중요하다고 생각합니다.