เทคนิค HTML5 สำหรับการเพิ่มประสิทธิภาพการทำงานบนมือถือ

เกริ่นนำ

ปัญหาเรื่องการรีเฟรช การเปลี่ยนหน้ากระตุก และความล่าช้าเป็นระยะๆ ในเหตุการณ์การแตะ ล้วนแล้วแต่เป็นเรื่องชวนปวดหัวในสภาพแวดล้อมของเว็บบนอุปกรณ์เคลื่อนที่ในปัจจุบัน นักพัฒนาซอฟต์แวร์กำลังพยายามสร้างความใกล้ชิดกับโฆษณาเนทีฟให้มากที่สุดเท่าที่จะทำได้ แต่มักจะถูกขัดขวางเนื่องจากการแฮ็ก การรีเซ็ต และเฟรมเวิร์กที่เข้มงวด

ในบทความนี้ เราจะพูดถึงขั้นตอนขั้นต่ำสุดในการสร้างเว็บแอป HTML5 บนอุปกรณ์เคลื่อนที่ โดยมีประเด็นสำคัญคือการเผยให้เห็นถึงความซับซ้อนที่แอบแฝงซึ่งเฟรมเวิร์กบนอุปกรณ์เคลื่อนที่ในปัจจุบันพยายามซ่อนไว้ คุณจะเห็นแนวทางที่เรียบง่าย (ใช้ API หลักสำหรับ HTML5) และพื้นฐานพื้นฐานที่ช่วยให้คุณสามารถเขียนเฟรมเวิร์กของคุณเองหรือมีส่วนร่วมกับแนวทางที่คุณใช้อยู่ในปัจจุบัน

การเร่งฮาร์ดแวร์

โดยปกติ GPU จะจัดการกับการสร้างโมเดล 3 มิติหรือแผนภาพ CAD แบบละเอียด แต่ในกรณีนี้ เราต้องการให้ภาพวาดพื้นฐานของเรา (div, พื้นหลัง, ข้อความที่มีเงาตกกระทบ รูปภาพ ฯลฯ) แสดงอย่างลื่นไหลและสร้างภาพเคลื่อนไหวได้อย่างลื่นไหลผ่าน GPU สิ่งที่น่าเสียดายก็คือนักพัฒนาซอฟต์แวร์ส่วนหน้าส่วนใหญ่เปลี่ยนขั้นตอนการสร้างภาพเคลื่อนไหวนี้ไปยังเฟรมเวิร์กของบุคคลที่สามโดยไม่กังวลเกี่ยวกับอรรถศาสตร์ แต่ควรปกปิดฟีเจอร์หลักของ CSS3 เหล่านี้ไหม ฉันขอแสดงเหตุผล 2-3 ประการว่าเหตุใดการให้ความสำคัญกับเรื่องนี้จึงเป็นสิ่งสำคัญ

  1. การจัดสรรหน่วยความจำและภาระด้านการคำนวณ — หากคุณพยายามจัดวางองค์ประกอบทั้งหมดใน DOM เพื่อการเร่งฮาร์ดแวร์ บุคคลถัดไปที่ทำงานบนโค้ดของคุณอาจไล่คุณลงมาและเอาชนะคุณได้อย่างแรง

  2. การใช้พลังงาน - เห็นได้ชัดว่าเมื่อฮาร์ดแวร์ทำงาน แบตเตอรี่ก็เพิ่มขึ้นด้วย เมื่อพัฒนาสำหรับมือถือ นักพัฒนาซอฟต์แวร์จะถูกบังคับให้พิจารณาข้อจำกัดต่างๆ ของอุปกรณ์ในขณะที่เขียนแอปพลิเคชันเว็บบนมือถือ ซึ่งจะพบเห็นได้บ่อยยิ่งขึ้นเนื่องจากผู้ผลิตเบราว์เซอร์เริ่มเปิดให้มีการเข้าถึงฮาร์ดแวร์อุปกรณ์มากขึ้นเรื่อยๆ

  3. ความขัดแย้ง - ฉันพบการทำงานที่ผิดปกติเมื่อใช้การเร่งฮาร์ดแวร์กับบางส่วนของหน้าที่มีการเร่งความเร็วไปแล้ว ดังนั้นการทราบความเร่งที่ซ้อนทับกันถือเป็นอย่างมาก

เราต้องทำให้เบราว์เซอร์ทำงานได้ดียิ่งขึ้นเพื่อให้ผู้ใช้โต้ตอบได้อย่างราบรื่นและใกล้เคียงกับเนทีฟมากที่สุด ตามหลักการแล้ว เราต้องการให้ CPU ของอุปกรณ์เคลื่อนที่ตั้งค่าภาพเคลื่อนไหวเริ่มต้น จากนั้นให้ GPU รับผิดชอบการประกอบเลเยอร์ต่างๆ ระหว่างกระบวนการภาพเคลื่อนไหวเท่านั้น ซึ่งเป็นหน้าที่ของ translate3d,scale3d และ translateZ โดยองค์ประกอบที่เคลื่อนไหวได้มีเลเยอร์ของตนเอง จึงทำให้อุปกรณ์แสดงผลทุกอย่างร่วมกันได้อย่างราบรื่น หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับการเร่งการประมวลผลและวิธีการทำงานของ WebKit Ariya Hidayat มีข้อมูลดีๆ มากมายในบล็อกของเขา

การเปลี่ยนหน้า

มาดูวิธีโต้ตอบกับผู้ใช้ที่พบบ่อยที่สุด 3 วิธีในการพัฒนาเว็บแอปบนอุปกรณ์เคลื่อนที่ นั่นก็คือเอฟเฟกต์การเลื่อน พลิก และการหมุน

คุณสามารถดูการทำงานของโค้ดนี้ได้ที่นี่ http://slidfast.appspot.com/slide-flip-rotate.html (หมายเหตุ: การสาธิตนี้สร้างขึ้นสำหรับอุปกรณ์เคลื่อนที่ ดังนั้นให้เริ่มการทำงานของโปรแกรมจำลอง ใช้โทรศัพท์หรือแท็บเล็ต หรือลดขนาดหน้าต่างเบราว์เซอร์เป็นประมาณ 1024px หรือน้อยกว่า)

ขั้นแรก เราจะวิเคราะห์การเปลี่ยนแบบสไลด์ พลิก และหมุน รวมถึงวิธีเร่งการเปลี่ยนสไลด์ สังเกตว่าภาพเคลื่อนไหวแต่ละภาพใช้ CSS และ JavaScript เพียง 3-4 บรรทัด

การเลื่อน

วิธีการเปลี่ยน 3 วิธีที่ใช้กันมากที่สุดคือ การเปลี่ยนหน้าแบบเลื่อนจะเลียนแบบลักษณะดั้งเดิมของแอปพลิเคชันบนอุปกรณ์เคลื่อนที่ มีการเปลี่ยนแปลงสไลด์เพื่อนำพื้นที่เนื้อหาใหม่ไว้ในวิวพอร์ต

สำหรับเอฟเฟกต์สไลด์ เราจะประกาศมาร์กอัปของเราก่อน ดังนี้

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

เมื่อผู้ใช้คลิกองค์ประกอบการนำทาง เราจะเรียกใช้ 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 เวอร์ชันเดสก์ท็อปจะเร่งองค์ประกอบที่มีการเปลี่ยนรูปแบบทั้งหมด (ไม่ว่ารูปแบบจะเป็น 2-D หรือ 3-D) ก็ตาม จึงเป็นการเหมาะสมที่จะสร้างคิวรี่สื่อและไม่รวมการเร่งความเร็วในระดับนั้น โปรดทราบว่าเทคนิคการเร่งความเร็วด้วยฮาร์ดแวร์ไม่ได้มีการปรับปรุงความเร็วใดๆ ใน 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';
}

ฉันลองเล่นกับลูกบาศก์เบซิเยร์เพื่อให้ความรู้สึกเป็นภาษาพื้นเมืองที่ดีที่สุดสำหรับการเปลี่ยนภาพ แต่การค่อยๆ เปลี่ยนนั้นทำได้

สุดท้าย ในการทำให้การนําทางเกิดขึ้น เราต้องเรียกใช้เมธอด slideTo() ที่กําหนดไว้ก่อนหน้านี้ซึ่งใช้ในการสาธิตครั้งล่าสุด

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

การหมุน

ถัดไป เรามาดูภาพเคลื่อนไหวแบบหมุนที่ใช้ในการสาธิตนี้กัน คุณสามารถหมุนหน้าที่กำลังดู 180 องศาได้ตลอดเวลาเพื่อแสดงอีกด้านหนึ่งโดยแตะที่ตัวเลือกเมนู "รายชื่อติดต่อ" ขอย้ำอีกครั้งว่า การกำหนดคลาสการเปลี่ยน onclick ใช้ CSS และ JavaScript เพียงไม่กี่บรรทัดเท่านั้น หมายเหตุ: การเปลี่ยนแบบหมุนจะแสดงผลไม่ถูกต้องใน Android เกือบทุกเวอร์ชัน เนื่องจากไม่มีความสามารถในการเปลี่ยนรูปแบบ CSS แบบ 3 มิติ 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);
}

การแก้ปัญหาการเร่งฮาร์ดแวร์

เมื่อพูดถึงการเปลี่ยนขั้นพื้นฐานแล้ว เรามาดูกลไกการทำงานและการประกอบกัน

ในการทำให้เซสชันการแก้ไขข้อบกพร่องที่ยอดเยี่ยมนี้เกิดขึ้น เรามาเริ่มการทำงานของเบราว์เซอร์ 2 อย่างและ IDE ที่คุณเลือก ก่อนอื่นให้เริ่มต้น Safari จากบรรทัดคำสั่งเพื่อใช้ประโยชน์จากตัวแปรสภาพแวดล้อมการแก้ไขข้อบกพร่อง ตอนนี้ฉันใช้ Mac คำสั่งจึงอาจแตกต่างกันไปตามระบบปฏิบัติการของคุณ เปิดเทอร์มินัลแล้วพิมพ์คำสั่งต่อไปนี้

  • $> ส่งออก CA_COLOR_OPAQUE=1
  • $> ส่งออก CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

การดำเนินการนี้จะเริ่มต้น Safari ด้วยตัวช่วยเหลือในการดีบัก 2-3 ตัว CA_COLOR_OPAQUE แสดงให้เราเห็นว่าองค์ประกอบใดที่มีการทำการผสมหรือแบบเร่ง CA_LOG_MEMORY_USAGE แสดงให้เห็นว่าเราใช้หน่วยความจำมากเพียงใดเมื่อส่งการวาดภาพไปยังสโตร์สำรอง ซึ่งจะบอกให้คุณทราบถึงปริมาณการใช้งานในอุปกรณ์เคลื่อนที่ รวมทั้งอาจให้คำใบ้ว่าการใช้ GPU อาจทำให้แบตเตอรี่ของอุปกรณ์เป้าหมายหมดไปอย่างไรบ้าง

ทีนี้จะเริ่มการทำงานของ Chrome เพื่อดูข้อมูลของเฟรมต่อวินาที (FPS) ที่ดี

  1. เปิดเว็บเบราว์เซอร์ Google Chrome
  2. ในแถบ URL ให้พิมพ์ about:flags
  3. เลื่อนลงมา 2-3 รายการแล้วคลิก "เปิดใช้" สำหรับตัวนับ FPS

ถ้าคุณดูหน้านี้ใน Chrome เวอร์ชันซุปเปอร์ คุณจะเห็นตัวนับ FPS สีแดงที่มุมซ้ายบน

FPS ของ Chrome

ซึ่งทำให้เราทราบว่าการเร่งฮาร์ดแวร์เปิดอยู่ นอกจากนี้ยังช่วยให้เราเห็นภาพว่าภาพเคลื่อนไหวทำงานเป็นอย่างไร และมีรอยรั่วหรือไม่ (ควรหยุดภาพเคลื่อนไหวแบบต่อเนื่อง) หรือไม่

อีกวิธีหนึ่งในการดูการเร่งฮาร์ดแวร์ได้จริงๆ คือคุณเปิดหน้าเว็บเดียวกันใน Safari (โดยใช้ตัวแปรสภาพแวดล้อมที่กล่าวไว้ข้างต้น) องค์ประกอบ DOM แบบเร่งทั้งหมดมีสีแดงจาง ซึ่งจะแสดงให้เราเห็นว่ามีการผสมเลเยอร์อะไรไว้บ้าง สังเกตได้ว่าการนำทางสีขาวไม่ใช่สีแดงเพราะไม่ได้มีการเร่ง

รายชื่อติดต่อแบบผสม

การตั้งค่าที่คล้ายกันสำหรับ Chrome มีอยู่ใน about:flags ใน "เส้นขอบของเลเยอร์แสดงผลแบบผสม"

อีกวิธีที่ดีในการดูเลเยอร์ผสมคือการดูการสาธิตใบไม้ร่วงของ WebKit ขณะที่มีการใช้ม็อดนี้

ใบไม้

และสุดท้าย เพื่อให้เข้าใจถึงประสิทธิภาพของฮาร์ดแวร์กราฟิกของแอปพลิเคชันของเราอย่างแท้จริง เราจะมาดูการใช้งานหน่วยความจำ จากตรงนี้จะเห็นว่าเราส่งวิธีการวาดภาพขนาด 1.38 MB ไปยังบัฟเฟอร์ CoreAnimation บน Mac OS บัฟเฟอร์หน่วยความจำหลักของภาพเคลื่อนไหวจะแชร์กันระหว่าง OpenGL ES และ GPU เพื่อสร้างพิกเซลสุดท้ายที่คุณเห็นในหน้าจอ

Coreanimation 1

เมื่อเราปรับขนาดหรือขยายหน้าต่างเบราว์เซอร์ให้ใหญ่ที่สุด เราจะเห็นว่าหน่วยความจำที่ขยายนั้นเพิ่มขึ้นด้วย

Coreanimation 2

ข้อมูลนี้จะบอกให้คุณทราบถึงการใช้หน่วยความจำบนอุปกรณ์เคลื่อนที่เฉพาะเมื่อคุณปรับขนาดเบราว์เซอร์ให้อยู่ในขนาดที่ถูกต้องเท่านั้น หากกำลังแก้ไขข้อบกพร่องหรือทดสอบสภาพแวดล้อมของ iPhone ให้ปรับขนาดเป็น 480 x 320 พิกเซล ตอนนี้เราเข้าใจจริงๆ ว่าการเร่งฮาร์ดแวร์ทำงานอย่างไรและต้องใช้อะไรในการแก้ปัญหา การอ่านเรื่องนี้เป็นเรื่องหนึ่ง แต่การได้เห็นบัฟเฟอร์หน่วยความจำ GPU ที่ทำงานอยู่จริงๆ ทำให้เห็นภาพจริงๆ

เบื้องหลัง: การดึงข้อมูลและการแคช

ตอนนี้ได้เวลายกระดับการแคชหน้าและทรัพยากรของเราแล้ว เราจะดึงข้อมูลล่วงหน้าและแคชหน้าเว็บด้วยการเรียกใช้ AJAX ที่เกิดขึ้นพร้อมกัน เช่นเดียวกับแนวทางที่ JQuery Mobile และเฟรมเวิร์กที่คล้ายกันใช้

มาแก้ปัญหาหลักๆ ของเว็บบนอุปกรณ์เคลื่อนที่และเหตุผลที่เราต้องแก้ปัญหานี้กัน

  • การดึงข้อมูล: การดึงข้อมูลหน้าเว็บล่วงหน้าช่วยให้ผู้ใช้ใช้แอปแบบออฟไลน์ได้ และช่วยให้ไม่ต้องรอระหว่างการดำเนินการนำทางต่างๆ แน่นอนว่าเราไม่อยากใช้แบนด์วิดท์ของอุปกรณ์จนหมดเมื่ออุปกรณ์ออนไลน์ ดังนั้นเราจำเป็นต้องใช้ฟีเจอร์นี้เท่าที่จำเป็น
  • การแคช: ต่อไป เราต้องการวิธีที่จะนำไปใช้ได้พร้อมกันหรือไม่พร้อมกันเมื่อดึงและแคชหน้าเว็บเหล่านี้ นอกจากนี้เรายังต้องใช้ localStorage (เนื่องจากสามารถใช้งานได้ดีในอุปกรณ์) ซึ่งไม่สามารถใช้ทำงานพร้อมกันได้
  • AJAX และการแยกวิเคราะห์การตอบกลับ: การใช้ inlineHTML() เพื่อแทรกการตอบสนอง AJAX ลงใน DOM นั้นเป็นอันตราย (และไม่น่าเชื่อถือ) เราจะใช้กลไกที่เชื่อถือได้สำหรับการแทรกการตอบสนองของ AJAX และการจัดการการเรียกใช้พร้อมกัน นอกจากนี้ เรายังใช้ประโยชน์จากคุณลักษณะใหม่บางอย่างของ HTML5 ในการแยกวิเคราะห์ xhr.responseText

การต่อยอดจากโค้ดจากการสาธิตสไลด์ พลิก และหมุน เราเริ่มต้นด้วยการเพิ่มหน้ารองแล้วลิงก์กับหน้าเหล่านั้น จากนั้นเราจะแยกวิเคราะห์ลิงก์และสร้างการเปลี่ยนภาพทันที

หน้าแรกของ iPhone

ดูการสาธิตการดึงข้อมูลและแคชที่นี่

คุณจะเห็นว่าเรากำลังใช้ประโยชน์จากมาร์กอัปเชิงความหมายที่นี่ เป็นเพียงลิงก์ไปยังหน้าอื่น หน้าย่อยใช้โครงสร้างโหนด/คลาสเดียวกันกับระดับบนสุด เราสามารถพัฒนาไปอีกขั้นและใช้แอตทริบิวต์ data-* สำหรับโหนด “page” ฯลฯ และนี่คือหน้ารายละเอียด (ระดับย่อย) ที่อยู่ในไฟล์ 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” ประมวลผลหลังการประมวลผลแบบไม่พร้อมกันอย่างถูกต้อง มีคำอธิบายขั้นสูงกว่าเกี่ยวกับการใช้ localStorage ภายในการเรียกใช้ AJAX ใน การทำงานนอกตารางกริดด้วย 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 ส่วนสาเหตุทั้งหมดในการดึงข้อมูลและการแคชหน้า/มาร์กอัปเหล่านี้นอกขอบเขตแคชของแอปพลิเคชันจะแสดงอยู่ในส่วนถัดไป

ความก้าวหน้าล่าสุดในองค์ประกอบ iframe ที่มี HTML5 ทำให้ตอนนี้เรามีวิธีง่ายๆ และมีประสิทธิภาพในการแยกวิเคราะห์ responseText ที่เราได้รับจากการเรียก AJAX มีโปรแกรมแยกวิเคราะห์ JavaScript และนิพจน์ทั่วไปที่มีจำนวน 3,000 บรรทัดมากมาย ซึ่งจะนำแท็กสคริปต์ออก และอื่นๆ แล้วทำไมไม่ปล่อยให้เบราว์เซอร์ทำในสิ่งที่ถนัดที่สุดล่ะ ในตัวอย่างนี้ เราจะเขียน responseText ลงใน iframe ที่ซ่อนแบบชั่วคราว เราใช้แอตทริบิวต์ “sandbox” HTML5 ซึ่งปิดใช้สคริปต์และนำเสนอคุณลักษณะด้านความปลอดภัยมากมาย...

จากข้อกำหนดดังกล่าว เมื่อระบุ แอตทริบิวต์แซนด์บ็อกซ์จะเปิดใช้ชุดข้อจำกัดเพิ่มเติมในเนื้อหาที่โฮสต์ด้วย iframe ค่าของโทเค็นนี้ต้องเป็นชุดโทเค็นที่คั่นด้วยช่องว่างที่ไม่ซ้ำกันซึ่งไม่เรียงลำดับ และไม่คำนึงถึงตัวพิมพ์เล็กหรือใหญ่ของ ASCII ค่าที่อนุญาต ได้แก่ แบบฟอร์มอนุญาต, อนุญาตต้นทางเดียวกัน, อนุญาตสคริปต์ และอนุญาตการนำทางด้านบน เมื่อตั้งค่าแอตทริบิวต์แล้ว ระบบจะถือว่าเนื้อหามาจากต้นทางที่ไม่ซ้ำกัน ปิดใช้แบบฟอร์มและสคริปต์ ป้องกันไม่ให้ลิงก์กำหนดเป้าหมายบริบทการท่องเว็บอื่นๆ และปิดใช้ปลั๊กอิน

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 เหตุใดจึงไม่ใช้ inlineHTML เพียงอย่างเดียว แม้ว่า inlineHTML จะเป็นส่วนหนึ่งของข้อกำหนด HTML5 แล้ว แต่การแทรกการตอบสนองจากเซิร์ฟเวอร์ (สิ่งชั่วร้ายหรือความดี) ลงในพื้นที่ที่ไม่มีการทำเครื่องหมายถือเป็นอันตราย ระหว่างเขียนบทความนี้ เราไม่เจอใครที่ใช้คำใดๆ นอกจาก InsideHTML ฉันทราบว่า JQuery ใช้งานเป็นแกนหลัก พร้อมด้วยการต่อท้ายรายการสำรองในข้อยกเว้นเท่านั้น และ JQuery Mobile ก็นำมาใช้เช่นกัน อย่างไรก็ตาม เรายังไม่ได้ทำการทดสอบที่หนักหน่วงเกี่ยวกับ inlineHTML "หยุดทำงานแบบสุ่ม" แต่เราอยากได้เห็นแพลตฟอร์มทั้งหมดที่ได้รับผลกระทบนี้ ก็คงน่าสนใจว่าแนวทางใดมีประสิทธิภาพมากกว่ากัน... ผมก็ได้ทราบข่าวจากทั้ง 2 ฝ่ายเกี่ยวกับเรื่องนี้เช่นกัน

การตรวจจับ การจัดการ และการทำโปรไฟล์ประเภทเครือข่าย

ตอนนี้เราสามารถบัฟเฟอร์ (หรือแคชการคาดการณ์) ของเว็บแอปได้แล้ว เราจึงต้องให้ฟีเจอร์การตรวจหาการเชื่อมต่อที่เหมาะสมซึ่งจะทำให้แอปของเราฉลาดขึ้น ซึ่งเป็นจุดที่การพัฒนาแอปบนอุปกรณ์เคลื่อนที่มีความละเอียดอ่อนอย่างมากกับโหมดออนไลน์/ออฟไลน์และความเร็วในการเชื่อมต่อ ป้อน 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 ด้านบน เราต้องบอกโค้ดของเราหากมีการเรียกจากเหตุการณ์หรือคำขอหน้าเว็บจริงหรือการรีเฟรช สาเหตุหลักเกิดจากระบบจะไม่เริ่มเหตุการณ์เนื้อหา 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 แต่ผมแค่บอกให้เรียกทรัพยากรแบบไม่พร้อมกัน (จริง) หรือซิงโครนัส (เท็จ) สำหรับการเชื่อมต่อที่ระบุ

ไทม์ไลน์คำขอ Edge (ซิงโครนัส)

ซิงค์ Edge

ไทม์ไลน์คำขอ Wi-Fi (อะซิงโครนัส)

Wi-Fi ไม่พร้อมกัน

วิธีนี้ช่วยให้คุณปรับประสบการณ์ของผู้ใช้ได้อย่างน้อย 1 วิธี โดยอิงตามการเชื่อมต่อที่ช้าหรือเร็ว วิธีการนี้ไม่ใช่ทางออกในทุกกรณี สิ่งที่ต้องทำอีกอย่างคือการสร้างโมดัลการโหลดเมื่อมีการคลิกลิงก์ (เมื่อการเชื่อมต่อช้า) ในขณะที่แอปอาจยังคงดึงหน้าเว็บของลิงก์นั้นในเบื้องหลัง จุดสำคัญคือการลดเวลาในการตอบสนองไปพร้อมกับการใช้ประโยชน์จากความสามารถทั้งหมดของการเชื่อมต่อของผู้ใช้กับ HTML5 ล่าสุดและดีที่สุด ดูการสาธิตการตรวจจับเครือข่ายที่นี่

บทสรุป

เส้นทางแอป HTML5 บนอุปกรณ์เคลื่อนที่นั้นเพิ่งเริ่มต้นขึ้น แต่ตอนนี้คุณจะเห็นรากฐานที่เรียบง่ายและพื้นฐานมากๆ ของ "เฟรมเวิร์ก" บนอุปกรณ์เคลื่อนที่ซึ่งสร้างจาก HTML5 เพียงอย่างเดียวและรองรับเทคโนโลยีดังกล่าวด้วย ฉันคิดว่าการที่นักพัฒนาแอปต้องทำงานร่วมกันและจัดการฟีเจอร์เหล่านี้เป็นหลัก และไม่ปิดบังด้วย Wrapper