Kỹ thuật HTML5 để tối ưu hóa hiệu suất trên thiết bị di động

Wesley Hales
Wesley Hales

Giới thiệu

Làm mới liên tục, chuyển đổi trang bị giật và sự chậm trễ định kỳ trong các sự kiện nhấn chỉ là một vài trong số những vấn đề đau đầu trong môi trường web dành cho thiết bị di động hiện nay. Các nhà phát triển đang cố gắng tiếp cận ứng dụng gốc nhất có thể, nhưng thường bị ảnh hưởng bởi hành vi tấn công, đặt lại và khung cứng.

Trong bài viết này, chúng tôi sẽ thảo luận về những yếu tố tối thiểu cần thiết để tạo một ứng dụng web HTML5 dành cho thiết bị di động. Điểm chính là làm sáng tỏ những phức tạp tiềm ẩn mà các khung thiết bị di động ngày nay cố gắng che giấu. Bạn sẽ thấy phương pháp tiếp cận tối giản (sử dụng API HTML5 cốt lõi) và các nguyên tắc cơ bản cơ bản sẽ cho phép bạn viết khung của riêng mình hoặc đóng góp vào khung mà bạn hiện đang sử dụng.

Tăng tốc phần cứng

Thông thường, GPU xử lý mô hình 3D chi tiết hoặc sơ đồ CAD, nhưng trong trường hợp này, chúng ta muốn các bản vẽ gốc (div, nền, văn bản có bóng đổ, hình ảnh, v.v.) xuất hiện mượt mà và tạo ảnh động mượt mà thông qua GPU. Điều đáng tiếc là hầu hết các nhà phát triển giao diện người dùng đều chuyển quá trình ảnh động này sang khung của bên thứ ba mà không quan tâm đến ngữ nghĩa, nhưng liệu các tính năng CSS3 cốt lõi này có nên được che giấu không? Hãy để tôi đưa ra một vài lý do tại sao bạn phải quan tâm đến vấn đề này:

  1. Phân bổ bộ nhớ và gánh nặng tính toán — Nếu bạn tổng hợp mọi phần tử trong DOM chỉ vì mục đích tăng tốc phần cứng, thì người tiếp theo phụ trách mã của bạn có thể sẽ đuổi theo và đánh bạn một cách gay gắt.

  2. Mức tiêu thụ năng lượng – Rõ ràng là khi phần cứng hoạt động thì pin cũng vậy. Khi phát triển cho thiết bị di động, các nhà phát triển buộc phải xem xét nhiều hạn chế đối với thiết bị khi viết ứng dụng web dành cho thiết bị di động. Điều này sẽ còn phổ biến hơn khi các nhà sản xuất trình duyệt bắt đầu cho phép truy cập vào ngày càng nhiều phần cứng thiết bị.

  3. Xung đột — Tôi đã gặp phải hành vi không mong muốn khi áp dụng tăng tốc phần cứng cho các phần của trang đã được tăng tốc. Vì vậy, việc biết liệu bạn có tăng tốc trùng lặp rất quan trọng hay không.

Để việc tương tác của người dùng diễn ra suôn sẻ và gần giống với bản địa nhất có thể, chúng tôi phải làm cho trình duyệt hoạt động cho chúng tôi. Tốt nhất là chúng ta nên CPU thiết bị di động thiết lập ảnh động ban đầu, sau đó yêu cầu GPU chịu trách nhiệm chỉ kết hợp các lớp khác nhau trong quá trình tạo ảnh động. Đây là những gì translation3d, scale3d và translationZ thực hiện – chúng cung cấp cho các phần tử ảnh động lớp riêng của chúng, do đó cho phép thiết bị kết xuất tất cả mọi thứ cùng nhau một cách trơn tru. Để tìm hiểu thêm về việc kết hợp tăng tốc và cách hoạt động của truy cập bằng tin bài, Ariya Hidayat có nhiều thông tin hữu ích trên blog của mình.

Chuyển đổi trang

Hãy cùng tìm hiểu ba phương pháp tiếp cận tương tác phổ biến nhất của người dùng khi phát triển ứng dụng web dành cho thiết bị di động: hiệu ứng trượt, lật và xoay.

Bạn có thể xem mã này trong thực tế tại đây http://slidfast.appspot.com/slide-flip-rotate.html (Lưu ý: Bản minh hoạ này được xây dựng cho thiết bị di động, vì vậy, hãy kích hoạt trình mô phỏng, sử dụng điện thoại hoặc máy tính bảng của bạn hoặc giảm kích thước của cửa sổ trình duyệt xuống ~ 1024px trở xuống).

Trước tiên, chúng ta sẽ phân tích về các hiệu ứng chuyển đổi trong trang trình bày, lật và xoay cũng như cách chúng được tăng tốc. Lưu ý rằng mỗi ảnh động chỉ cần 3 hoặc 4 dòng CSS và JavaScript.

Trượt khối

Phổ biến nhất trong ba phương pháp chuyển đổi, chuyển tiếp trang trượt mô phỏng cảm giác gốc của ứng dụng dành cho thiết bị di động. Chuyển đổi trang trình bày được gọi để đưa một vùng nội dung mới vào khung xem.

Đối với hiệu ứng trang trình bày, trước tiên, chúng ta khai báo mã đánh dấu:

<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>

Vui lòng lưu ý cách chúng tôi sử dụng khái niệm phân chia các trang bên trái hoặc bên phải. Về cơ bản, bạn có thể xem theo hướng bất kỳ, nhưng đây là hướng phổ biến nhất.

Chúng tôi hiện đã có hoạt ảnh cộng với tăng tốc phần cứng chỉ với một vài dòng CSS. Ảnh động thực tế xảy ra khi chúng ta hoán đổi các lớp trên các phần tử div của trang.

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

translate3d(0,0,0) được gọi là phương pháp "giải pháp đạn bạc".

Khi người dùng nhấp vào một phần tử điều hướng, chúng ta sẽ thực thi JavaScript sau để hoán đổi các lớp. Không có khung của bên thứ ba nào đang được sử dụng, đây là 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 hoặc stage-right trở thành stage-center và buộc trang trượt vào chế độ xem chính giữa. Chúng tôi hoàn toàn phụ thuộc vào CSS3 để thực hiện phần việc khó khăn.

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

.stage-right {
  left: 480px;
}

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

Tiếp theo, hãy xem CSS xử lý việc phát hiện và hướng trên thiết bị di động. Chúng tôi có thể giải quyết mọi thiết bị và mọi độ phân giải (xem phần giải pháp truy vấn phương tiện). Tôi chỉ sử dụng một vài ví dụ đơn giản trong bản minh hoạ này để bao gồm hầu hết các chế độ xem dọc và ngang trên thiết bị di động. Việc này cũng hữu ích khi áp dụng tăng tốc phần cứng cho mỗi thiết bị. Ví dụ: vì phiên bản WebM dành cho máy tính để bàn tăng tốc tất cả các phần tử được chuyển đổi (bất kể là 2-D hay 3-D), nên việc tạo truy vấn phương tiện và loại trừ tăng tốc ở mức đó là hợp lý. Lưu ý rằng các thủ thuật tăng tốc phần cứng không cung cấp bất kỳ cải tiến tốc độ nào trong Android Froyo 2.2+. Tất cả quá trình kết hợp được thực hiện trong phần mềm.

/* 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;
  }
}

Lật ảnh

Trên thiết bị di động, thao tác lật được gọi là vuốt trang sang bên. Ở đây, chúng tôi sử dụng một số JavaScript đơn giản để xử lý sự kiện này trên các thiết bị iOS và Android (dựa trên phiên bản Tin nhắn).

Xem tiện ích này trong thực tế http://slidfast.appspot.com/slide-flip-rotate.html.

Khi xử lý các sự kiện nhấn và chuyển đổi, điều đầu tiên bạn cần là xử lý vị trí hiện tại của phần tử. Xem tài liệu này để biết thêm thông tin về CGICSSMatrix.

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

Vì chúng tôi đang sử dụng quá trình chuyển đổi dễ dàng cho CSS3 để lật trang, nên element.offsetLeft thông thường sẽ không hoạt động.

Tiếp theo, chúng ta cần xác định hướng mà người dùng đang lật ngược và đặt ngưỡng cho một sự kiện (điều hướng trang) diễn ra.

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;
  }
}

Bạn cũng sẽ nhận thấy rằng chúng ta cũng đang đo lường swipeTime theo mili giây. Thao tác này cho phép kích hoạt sự kiện điều hướng nếu người dùng vuốt nhanh màn hình để lật trang.

Để định vị trang và làm cho ảnh động trông giống như gốc trong khi một ngón tay đang chạm vào màn hình, chúng tôi sử dụng chuyển đổi CSS3 sau mỗi sự kiện kích hoạt.

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';
}

Tôi đã cố gắng sử dụng khối hình khối để mang lại cảm giác tự nhiên tốt nhất cho quá trình chuyển đổi, nhưng việc dễ dàng đã có hiệu quả.

Cuối cùng, để điều hướng diễn ra, chúng ta phải gọi các phương thức slideTo() đã xác định trước đó mà chúng ta đã sử dụng trong bản minh hoạ gần đây nhất.

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

Xoay

Tiếp theo, hãy xem ảnh động xoay được dùng trong bản minh hoạ này. Bạn có thể xoay trang bạn đang xem 180 độ bất cứ lúc nào để hiển thị phía ngược lại bằng cách nhấn vào lựa chọn trình đơn "Liên hệ". Xin nhắc lại, việc này chỉ mất một vài dòng CSS và một số JavaScript để chỉ định lớp chuyển đổi onclick. LƯU Ý: Chuyển đổi xoay không được hiển thị chính xác trên hầu hết các phiên bản Android do thiếu tính năng chuyển đổi CSS 3D. Thật không may, thay vì bỏ qua việc lật, Android sẽ làm cho trang "đẩy bánh" qua đi bằng cách xoay thay vì lật. Bạn nên hạn chế sử dụng quá trình chuyển đổi này cho đến khi khả năng hỗ trợ được cải thiện.

Mã đánh dấu (khái niệm cơ bản về phần trước và sau):

<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);
}

Gỡ lỗi tăng tốc phần cứng

Chúng ta hiện đã tìm hiểu xong về các phương pháp chuyển đổi cơ bản, bây giờ hãy tìm hiểu cơ chế hoạt động và các phương pháp tổng hợp của các phương pháp này.

Để phiên gỡ lỗi kỳ diệu này diễn ra, hãy kích hoạt một số trình duyệt và IDE mà bạn chọn. Trước tiên, hãy khởi động Safari từ dòng lệnh để sử dụng một số biến môi trường gỡ lỗi. Tôi đang dùng máy Mac, vì vậy, các lệnh có thể khác nhau tuỳ theo hệ điều hành của bạn. Mở cửa sổ dòng lệnh và nhập như sau:

  • $> xuất CA_COLOR_OPAQUE=1
  • $> xuất CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

Thao tác này sẽ khởi động Safari bằng một số trình trợ giúp gỡ lỗi. CA_COLOR_OPAQUE cho chúng ta thấy các phần tử nào thực sự được kết hợp hoặc tăng tốc. CA_LOG_MEMORY_USAGE cho chúng ta biết mức bộ nhớ chúng ta sử dụng khi gửi các thao tác vẽ đến kho lưu trữ sao lưu. Số liệu này cho bạn biết chính xác mức độ căng thẳng mà bạn đang gây ra trên thiết bị di động và có thể đưa ra gợi ý về cách mức sử dụng GPU của bạn có thể đang tiêu hao pin của thiết bị mục tiêu.

Bây giờ, hãy kích hoạt Chrome để chúng ta có thể xem một số thông tin về khung hình/giây (FPS) phù hợp:

  1. Mở trình duyệt web Google Chrome.
  2. Trong thanh URL, nhập about:flags.
  3. Di chuyển xuống một vài mục rồi nhấp vào "Bật" cho Bộ đếm FPS.

Nếu xem trang này trong phiên bản Chrome đã được cải tiến, bạn sẽ thấy bộ đếm FPS màu đỏ ở góc trên cùng bên trái.

FPS trên Chrome

Đây là cách chúng ta biết tính năng tăng tốc phần cứng đang bật. Bài viết này cũng cho chúng tôi ý tưởng về cách chạy ảnh động và liệu bạn có bị rò rỉ nào không (các ảnh động chạy liên tục nên được dừng).

Một cách khác để thực sự trực quan hoá việc tăng tốc phần cứng là nếu bạn mở cùng một trang trong Safari (với các biến môi trường mà tôi đã đề cập ở trên). Mọi phần tử DOM được tăng tốc đều có một sắc thái màu đỏ. Điều này cho chúng ta thấy chính xác những gì đang được kết hợp theo lớp. Hãy lưu ý phần điều hướng màu trắng không phải là màu đỏ vì chưa được tăng tốc.

Liên hệ tổng hợp

Cài đặt tương tự cho Chrome cũng có trong about:flags "Đường viền lớp kết xuất tổng hợp".

Một cách tuyệt vời khác để xem các lớp tổng hợp là xem bản minh hoạ lá rơi AppCompat trong khi áp dụng bản mod này.

Lá rụng trứng

Cuối cùng, để thực sự hiểu rõ hiệu suất phần cứng đồ hoạ của ứng dụng, hãy cùng xem mức sử dụng bộ nhớ. Ở đây, chúng ta thấy chúng ta đang đẩy 1,38 MB lệnh vẽ vào vùng đệm CoreAnimation trên Mac OS. Vùng đệm bộ nhớ Core Animation được dùng chung giữa OpenGL ES và GPU để tạo các pixel cuối cùng mà bạn thấy trên màn hình.

Coreanimation 1

Khi chỉ cần thay đổi kích thước hoặc phóng to cửa sổ trình duyệt, chúng tôi cũng sẽ thấy bộ nhớ mở rộng.

Coreanimation 2

Việc này cho bạn biết cách bộ nhớ đang được sử dụng trên thiết bị di động của bạn chỉ khi bạn thay đổi kích thước trình duyệt theo đúng kích thước. Nếu bạn đang gỡ lỗi hoặc thử nghiệm môi trường iPhone, hãy đổi kích thước thành 480px x 320px. Giờ đây, chúng ta đã hiểu chính xác cách hoạt động của tính năng tăng tốc phần cứng và các yếu tố cần thiết để gỡ lỗi. Đó là một điều để đọc về nó, nhưng thực sự thấy các vùng đệm bộ nhớ GPU hoạt động trực quan sẽ thực sự mang mọi thứ vào góc nhìn.

Cảnh hậu trường: Tìm nạp và lưu vào bộ nhớ đệm

Giờ đã đến lúc nâng cấp trang và tính năng lưu tài nguyên vào bộ nhớ đệm của chúng ta lên một tầm cao mới. Giống như phương pháp mà JQuery Mobile và các khung tương tự sử dụng, chúng tôi sẽ tìm nạp trước và lưu vào bộ nhớ đệm các trang của mình với các lệnh gọi AJAX đồng thời.

Hãy cùng giải quyết một số vấn đề chính về web dành cho thiết bị di động và lý do chúng ta cần thực hiện việc này:

  • Tìm nạp: Việc tìm nạp trước trang của chúng tôi cho phép người dùng tải ứng dụng xuống để xem khi không có mạng, đồng thời cũng cho phép người dùng không phải chờ giữa các thao tác di chuyển. Tất nhiên, chúng ta không muốn tắc nghẽn băng thông của thiết bị khi thiết bị kết nối mạng, vì vậy chúng ta cần sử dụng tính năng này một cách thận trọng.
  • Lưu vào bộ nhớ đệm: Tiếp theo, chúng ta cần một phương pháp đồng thời hoặc không đồng bộ khi tìm nạp và lưu các trang này vào bộ nhớ đệm. Chúng tôi cũng cần sử dụng localStorage (vì điều này được hỗ trợ tốt trong các thiết bị) và rất tiếc là không đồng bộ.
  • AJAX và phân tích cú pháp phản hồi: Việc sử dụng innerHTML() để chèn phản hồi AJAX vào DOM rất nguy hiểm (và không đáng tin cậy?). Thay vào đó, chúng tôi sử dụng một cơ chế đáng tin cậy để chèn phản hồi AJAX và xử lý các lệnh gọi đồng thời. Chúng tôi cũng tận dụng một số tính năng mới của HTML5 để phân tích cú pháp xhr.responseText.

Dựa trên mã từ bản minh hoạ Trượt, Lật và Xoay, chúng tôi bắt đầu bằng cách thêm một số trang phụ và liên kết đến chúng. Sau đó, chúng tôi sẽ phân tích cú pháp các liên kết và tạo chuyển đổi nhanh chóng.

Màn hình chính iPhone

Xem bản minh hoạ Tìm nạp và lưu vào bộ nhớ đệm tại đây.

Như bạn có thể thấy, chúng tôi đang sử dụng mã đánh dấu ngữ nghĩa ở đây. Chỉ là một đường liên kết đến một trang khác. Trang con tuân theo cùng một cấu trúc nút/lớp như trang gốc. Chúng ta có thể tiến thêm một bước nữa là sử dụng thuộc tính data-* cho các nút "page", v.v. Và đây là trang chi tiết (con) nằm trong một tệp html riêng biệt (/demo2/home-detail.html) sẽ được tải, lưu vào bộ nhớ đệm và thiết lập cho quá trình chuyển đổi khi tải ứng dụng.

<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>

Bây giờ, hãy xem qua JavaScript. Để đơn giản, tôi sẽ bỏ qua mọi trình trợ giúp hoặc tối ưu hoá. Tất cả những gì chúng ta đang làm ở đây là lặp lại một mảng các nút DOM được chỉ định để đào các đường liên kết đến tìm nạp và lưu vào bộ nhớ đệm. Lưu ý: Đối với bản minh hoạ này, phương thức fetchAndCache() sẽ được gọi khi tải trang. Chúng tôi sửa đổi lại trong phần tiếp theo khi phát hiện kết nối mạng và xác định thời điểm nên gọi kết nối.

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();
      }
    }
  }
};

Chúng tôi đảm bảo xử lý hậu kỳ không đồng bộ đúng cách thông qua việc sử dụng đối tượng "AJAX". Có phần giải thích nâng cao hơn về việc sử dụng localStorage trong lệnh gọi AJAX trong Xử lý lưới với HTML5 ngoại tuyến. Trong ví dụ này, bạn sẽ thấy cách sử dụng cơ bản của việc lưu vào bộ nhớ đệm đối với mỗi yêu cầu và cung cấp các đối tượng được lưu vào bộ nhớ đệm khi máy chủ trả về bất cứ điều gì ngoại trừ một phản hồi thành công (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;
      }
    }
  }
}

Rất tiếc, vì localStorage sử dụng UTF-16 để mã hoá ký tự, nên mỗi byte được lưu trữ dưới dạng 2 byte, nâng giới hạn bộ nhớ của chúng tôi từ 5 MB lên tổng cộng 2,6 MB. Toàn bộ lý do cho việc tìm nạp và lưu các trang/đánh dấu này vào bộ nhớ đệm bên ngoài phạm vi bộ nhớ đệm của ứng dụng sẽ được tiết lộ trong phần tiếp theo.

Với những tiến bộ gần đây trong phần tử iframe sử dụng HTML5, giờ đây chúng ta đã có một cách đơn giản và hiệu quả để phân tích cú pháp responseText mà chúng ta nhận được qua lệnh gọi AJAX. Có nhiều trình phân tích cú pháp JavaScript và biểu thức chính quy 3000 dòng giúp xoá thẻ tập lệnh, v.v. Nhưng tại sao không để trình duyệt làm điều tốt nhất? Trong ví dụ này, chúng ta sẽ ghi responseText vào một iframe bị ẩn tạm thời. Chúng tôi đang sử dụng thuộc tính “sandbox” HTML5 để vô hiệu hoá các tập lệnh và cung cấp nhiều tính năng bảo mật...

Từ thông số kỹ thuật: Thuộc tính hộp cát, khi được chỉ định, sẽ bật một tập hợp các hạn chế bổ sung đối với mọi nội dung do iframe lưu trữ. Giá trị của thuộc tính này phải là một tập hợp các mã thông báo duy nhất được phân tách bằng dấu cách không theo thứ tự, không phân biệt chữ hoa chữ thường theo ASCII. Các giá trị được phép là allow-forms, allow-same-origin, allow-scripts và allow-top-navigation. Khi bạn đặt thuộc tính này, nội dung được coi là xuất phát từ một nguồn gốc duy nhất, các biểu mẫu và tập lệnh bị tắt, các đường liên kết sẽ bị chặn nhắm đến các ngữ cảnh duyệt web khác và các trình bổ trợ bị tắt.

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 từ chối ngầm di chuyển nút từ tài liệu này sang tài liệu khác một cách chính xác. Lỗi sẽ xảy ra nếu nút con mới được tạo trong một tài liệu khác. Ở đây, chúng ta sử dụng adoptNode và mọi thứ đều ổn.

Vậy tại sao nên sử dụng iframe? Tại sao không chỉ sử dụng innerHTML? Mặc dù innerHTML hiện là một phần của thông số kỹ thuật HTML5, nhưng việc chèn phản hồi từ máy chủ (có hại hoặc tốt) vào khu vực chưa được đánh dấu là một phương pháp nguy hiểm. Trong quá trình viết bài viết này, tôi không tìm thấy bất kỳ ai sử dụng innerHTML. Tôi biết JQuery sử dụng nó là cốt lõi với tính năng dự phòng bổ sung chỉ cho trường hợp ngoại lệ. Và JQuery Mobile cũng sử dụng mã này. Tuy nhiên, tôi chưa từng thực hiện bất kỳ thử nghiệm nặng nề nào liên quan đến innerHTML "ngừng hoạt động ngẫu nhiên", nhưng sẽ rất thú vị khi thấy tất cả các nền tảng mà vấn đề này ảnh hưởng đến. Sẽ cũng rất thú vị khi biết phương pháp nào hiệu quả hơn... Tôi cũng đã nghe những lời tuyên bố từ cả hai bên về vấn đề này.

Phát hiện, xử lý và lập hồ sơ loại mạng

Giờ đây, khi đã có thể lưu vào bộ đệm (hoặc bộ nhớ đệm dự đoán) ứng dụng web, chúng ta phải cung cấp các tính năng phát hiện kết nối phù hợp để giúp ứng dụng thông minh hơn. Đây là lúc hoạt động phát triển ứng dụng di động trở nên cực kỳ nhạy cảm với chế độ trực tuyến/ngoại tuyến và tốc độ kết nối. Nhập Network Information API (API Thông tin mạng). Mỗi khi tôi trình bày tính năng này trong bài thuyết trình, một người trong khán giả sẽ giơ tay và hỏi "Tôi sẽ dùng tính năng đó để làm gì?". Vì vậy, đây là một cách khả thi để thiết lập một ứng dụng web cực kỳ thông minh dành cho thiết bị di động.

Trước tiên là cách tương tác với web bằng thiết bị di động trên tàu cao tốc, mạng có thể biến mất tại nhiều thời điểm và các khu vực địa lý khác nhau có thể hỗ trợ tốc độ truyền khác nhau (ví dụ: HSPA hoặc 3G có thể có ở một số khu vực đô thị, nhưng những khu vực xa xôi có thể hỗ trợ công nghệ 2G chậm hơn nhiều). Mã sau đây giải quyết hầu hết các trường hợp kết nối.

Mã sau đây cung cấp:

  • Truy cập ngoại tuyến thông qua applicationCache.
  • Phát hiện nếu đã đánh dấu trang và không có mạng.
  • Phát hiện khi chuyển từ chế độ ngoại tuyến sang trực tuyến và ngược lại.
  • Phát hiện các kết nối chậm và tìm nạp nội dung dựa trên loại mạng.

Xin nhắc lại, tất cả các tính năng này đều yêu cầu rất ít mã. Trước tiên, chúng tôi phát hiện các sự kiện và trường hợp tải:

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);

Trong EventListeners ở trên, chúng ta phải cho mã biết mã của mình có đang được gọi từ một sự kiện hoặc một yêu cầu hoặc làm mới trang thực tế. Lý do chính là vì sự kiện nội dung onload sẽ không được kích hoạt khi chuyển đổi giữa chế độ trực tuyến và ngoại tuyến.

Tiếp theo, chúng ta sẽ kiểm tra đơn giản cho sự kiện ononline hoặc onload. Mã này đặt lại các đường liên kết bị vô hiệu hoá khi chuyển từ chế độ ngoại tuyến sang trực tuyến. Tuy nhiên, nếu ứng dụng này tinh vi hơn, bạn có thể chèn logic để tiếp tục tìm nạp nội dung hoặc xử lý trải nghiệm người dùng cho các kết nối không liên tục.

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;
    }
  }
}

Điều này cũng áp dụng cho processOffline(). Tại đây, bạn sẽ sửa đổi ứng dụng của mình cho chế độ ngoại tuyến và cố gắng khôi phục mọi giao dịch đang diễn ra. Mã dưới đây giúp tìm ra tất cả các đường liên kết ngoài của chúng tôi và vô hiệu hoá chúng — khiến người dùng vĩnh viễn bị mắc kẹt trong ứng dụng ngoại tuyến của chúng tôi muhahaha!

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);

    }
  }
}

Được rồi, tiếp tục là nội dung thú vị. Giờ đây, ứng dụng của chúng ta đã biết trạng thái kết nối hiện tại, chúng ta cũng có thể kiểm tra loại kết nối khi ứng dụng có kết nối mạng và điều chỉnh cho phù hợp. Tôi đã liệt kê danh sách thời gian tải xuống và độ trễ thường thấy của các nhà cung cấp tại Bắc Mỹ trong phần nhận xét của mỗi kết nối.

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);
  }
}

Có rất nhiều điều chỉnh chúng ta có thể thực hiện đối với quá trình tìm nạpAndCache, nhưng tất cả những gì tôi đã làm ở đây chỉ là yêu cầu tìm nạp các tài nguyên không đồng bộ (true) hoặc đồng bộ (false) cho một kết nối nhất định.

Tiến trình yêu cầu Edge (Đồng bộ)

Đồng bộ hoá Edge

Tiến trình yêu cầu WIFI (Không đồng bộ)

Wi-Fi không đồng bộ

Điều này cho phép ít nhất một số phương pháp điều chỉnh trải nghiệm người dùng dựa trên kết nối chậm hoặc nhanh. Đây không phải là giải pháp toàn diện. Một việc cần làm khác là hiển thị một phương thức tải khi một đường liên kết được nhấp vào (trên kết nối chậm) trong khi ứng dụng vẫn có thể đang tìm nạp trang của đường liên kết đó ở chế độ nền. Điểm lớn ở đây là giảm độ trễ trong khi vẫn khai thác tối đa khả năng kết nối của người dùng với HTML5 mới nhất và tuyệt vời nhất có thể cung cấp. Xem bản minh hoạ tính năng phát hiện mạng tại đây.

Kết luận

Hành trình tìm hiểu ứng dụng HTML5 dành cho thiết bị di động chỉ mới bắt đầu. Giờ đây, bạn đã thấy các nền tảng rất đơn giản và cơ bản của “khung” di động được xây dựng chỉ dựa trên HTML5 và các công nghệ hỗ trợ của nó. Tôi nghĩ rằng các nhà phát triển cần phải làm việc và giải quyết những tính năng này về cốt lõi và không bị che giấu bằng trình bao bọc.