소개
오늘날의 모바일 웹 환경에서는 새로고침이 계속되고, 페이지 전환이 끊기고, 탭 이벤트가 주기적으로 지연되는 등 여러 가지 문제가 발생합니다. 개발자는 가능한 한 네이티브에 가까워지려고 노력하지만 해킹, 재설정, 엄격한 프레임워크로 인해 종종 어려움을 겪습니다.
이 도움말에서는 모바일 HTML5 웹 앱을 만드는 데 필요한 최소한의 사항을 설명합니다. 핵심은 오늘날의 모바일 프레임워크에서 숨기려고 하는 숨겨진 복잡성을 드러내는 것입니다. 핵심 HTML5 API를 사용하는 최소한의 접근 방식과 현재 사용하는 프레임워크를 직접 작성하거나 기여할 수 있는 기본 사항을 확인할 수 있습니다.
하드웨어 가속
일반적으로 GPU는 세부적인 3D 모델링이나 CAD 다이어그램을 처리하지만, 이 경우에는 기본 드로잉 (div, 배경, 그림자가 있는 텍스트, 이미지 등)이 GPU를 통해 매끄럽게 표시되고 매끄럽게 애니메이션 처리되기를 원합니다. 불행히도 대부분의 프런트엔드 개발자는 시맨틱에 신경 쓰지 않고 이 애니메이션 프로세스를 서드 파티 프레임워크에 넘기지만 이러한 핵심 CSS3 기능이 마스크되어야 할까요? 이러한 사항에 관심을 가져야 하는 몇 가지 이유를 알려드리겠습니다.
메모리 할당 및 계산 부담: 하드웨어 가속을 위해 DOM의 모든 요소를 합성하면 코드를 작업하는 다음 사람이 찾아와서 심하게 때릴 수 있습니다.
전력 소비 - 하드웨어가 작동하면 배터리도 작동합니다. 모바일용으로 개발할 때 개발자는 모바일 웹 앱을 작성하면서 다양한 기기 제약 조건을 고려해야 합니다. 브라우저 제작자가 점점 더 많은 기기 하드웨어에 대한 액세스를 지원하기 시작하면 이러한 현상은 더욱 두드러질 것입니다.
충돌: 이미 가속화된 페이지 부분에 하드웨어 가속을 적용할 때 글리치 동작이 발생했습니다. 따라서 중복되는 가속이 있는지 아는 것이 매우 중요합니다.
사용자 상호작용을 원활하게 하고 최대한 네이티브에 가깝게 만들려면 브라우저가 작동하도록 해야 합니다. 이상적으로는 모바일 기기 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-right이 stage-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';
}
전환에 최적의 네이티브 느낌을 주기 위해 cubic-bezier를 사용해 보았지만 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>
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) 정보를 확인해 보겠습니다.
- Google Chrome 웹브라우저를 엽니다.
- URL 표시줄에 about:flags를 입력합니다.
- 몇 개의 항목을 아래로 스크롤하여 FPS 카운터의 '사용 설정'을 클릭합니다.
강화된 버전의 Chrome에서 이 페이지를 보면 왼쪽 상단에 빨간색 FPS 카운터가 표시됩니다.
이렇게 하면 하드웨어 가속이 사용 설정되어 있는지 알 수 있습니다. 또한 애니메이션이 실행되는 방식과 누수 (중지되어야 하는 연속 실행 애니메이션)가 있는지 파악할 수 있습니다.
하드웨어 가속을 실제로 시각화하는 또 다른 방법은 위에 언급한 환경 변수를 사용하여 Safari에서 동일한 페이지를 여는 것입니다. 가속화된 모든 DOM 요소에는 빨간색 색조가 있습니다. 이렇게 하면 레이어별로 합성되는 내용을 정확하게 확인할 수 있습니다. 흰색 탐색은 가속화되지 않으므로 빨간색이 아닙니다.
Chrome의 유사한 설정은 about:flags의 '합성된 렌더링 레이어 테두리'에서도 사용할 수 있습니다.
합성된 레이어를 확인하는 또 다른 좋은 방법은 이 모드가 적용된 상태에서 WebKit 낙엽 데모를 보는 것입니다.
마지막으로 애플리케이션의 그래픽 하드웨어 성능을 제대로 이해하려면 메모리 소비 방식을 살펴보겠습니다. 여기에서는 Mac OS의 CoreAnimation 버퍼에 1.38MB의 그리기 명령어를 푸시하고 있습니다. Core Animation 메모리 버퍼는 OpenGL ES와 GPU 간에 공유되어 화면에 표시되는 최종 픽셀을 만듭니다.
브라우저 창의 크기를 조절하거나 최대화하면 메모리도 확장됩니다.
브라우저 크기를 올바른 크기로 조정한 경우에만 휴대기기에서 메모리가 어떻게 사용되는지 알 수 있습니다. iPhone 환경을 디버깅하거나 테스트하는 경우 480px x 320px로 크기를 조정합니다. 이제 하드웨어 가속이 작동하는 방식과 디버그하는 데 필요한 사항을 정확히 이해했습니다. GPU 메모리 버퍼에 대해 읽는 것과 실제로 GPU 메모리 버퍼가 시각적으로 작동하는 것을 보는 것은 큰 차이가 있습니다.
비하인드 스토리: 가져오기 및 캐싱
이제 페이지 및 리소스 캐싱을 한 단계 더 발전시킬 차례입니다. JQuery Mobile 및 유사한 프레임워크에서 사용하는 방식과 마찬가지로 동시 AJAX 호출을 사용하여 페이지를 미리 가져오고 캐시할 예정입니다.
몇 가지 핵심 모바일 웹 문제와 이를 해결해야 하는 이유를 살펴보겠습니다.
- 가져오기: 페이지를 미리 가져오면 사용자가 앱을 오프라인으로 사용할 수 있으며 탐색 작업 간에 기다릴 필요가 없습니다. 물론 기기가 온라인 상태가 될 때 기기의 대역폭을 제한하고 싶지는 않으므로 이 기능을 신중하게 사용해야 합니다.
- 캐싱: 다음으로 이러한 페이지를 가져오고 캐싱할 때 동시 또는 비동기 접근 방식이 필요합니다. 기기 간에 잘 지원되는 localStorage도 사용해야 하지만, localStorage는 비동기식이 아닙니다.
- AJAX 및 응답 파싱: innerHTML()을 사용하여 AJAX 응답을 DOM에 삽입하는 것은 위험합니다 (신뢰할 수 없음). 대신 AJAX 응답 삽입 및 동시 호출 처리를 위해 안정적인 메커니즘을 사용합니다. 또한
xhr.responseText를 파싱하기 위해 HTML5의 일부 새로운 기능을 활용합니다.
슬라이드, 뒤집기, 회전 데모의 코드를 기반으로 보조 페이지를 추가하고 연결하는 것으로 시작합니다. 그런 다음 링크를 파싱하고 즉석에서 전환을 만듭니다.
보시다시피 여기서는 시맨틱 마크업을 활용하고 있습니다. 다른 페이지로 연결되는 링크만 있습니다. 하위 페이지는 상위 페이지와 동일한 노드/클래스 구조를 따릅니다. 한 단계 더 나아가 '페이지' 노드 등에 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') &&
//'#' 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) &&
//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' 객체를 사용하여 적절한 비동기 후처리를 보장합니다. HTML5 오프라인으로 오프그리드 작업하기에서 AJAX 호출 내에 localStorage를 사용하는 방법을 자세히 설명합니다. 이 예에서는 각 요청에서 캐시를 기본적으로 사용하고 서버에서 성공 (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줄짜리 JavaScript 파서와 정규 표현식이 많이 있습니다. 하지만 브라우저가 가장 잘하는 일을 하도록 두는 것이 어떨까요? 이 예에서는 responseText을 임시 숨겨진 iframe에 작성합니다. 스크립트를 사용 중지하고 다양한 보안 기능을 제공하는 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이 무작위로 작동 중지되는 것과 관련해 집중적인 테스트를 진행하지는 않았습니다. 이 문제가 영향을 미치는 모든 플랫폼을 확인하는 것이 매우 흥미로울 것입니다. 어떤 접근 방식이 더 나은 성능을 보이는지도 흥미로울 것 같습니다. 이와 관련해서도 양쪽에서 주장을 들었습니다.
네트워크 유형 감지, 처리, 프로파일링
이제 웹 앱을 버퍼링 (또는 예측 캐싱)할 수 있으므로 앱을 더 스마트하게 만드는 적절한 연결 감지 기능을 제공해야 합니다. 이때 모바일 앱 개발은 온라인/오프라인 모드와 연결 속도에 매우 민감해집니다. 네트워크 정보 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에서 이벤트 또는 실제 페이지 요청이나 새로고침에서 호출되는지 코드로 알려야 합니다. 주된 이유는 온라인 모드와 오프라인 모드 간에 전환할 때 본문 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 (동기) 요청 타임라인
Wi-Fi (비동기) 요청 타임라인
이를 통해 느리거나 빠른 연결에 따라 사용자 환경을 조정하는 방법을 최소한 일부는 사용할 수 있습니다. 이것이 만능 해결책은 아닙니다. 또 다른 할 일은 앱이 백그라운드에서 해당 링크의 페이지를 가져오는 동안 링크를 클릭할 때 (느린 연결에서) 로드 모달을 표시하는 것입니다. 여기서 중요한 점은 최신 HTML5가 제공하는 모든 기능을 활용하면서 지연 시간을 줄이는 것입니다. 여기에서 네트워크 감지 데모를 확인하세요.
결론
모바일 HTML5 앱의 여정은 이제 시작에 불과합니다. 이제 HTML5와 지원 기술만을 기반으로 빌드된 모바일 '프레임워크'의 매우 간단하고 기본적인 토대를 확인할 수 있습니다. 개발자가 래퍼로 마스크 처리되지 않고 이러한 기능을 핵심에서 다루는 것이 중요하다고 생각합니다.