ปรับปรุงการไหลของผู้ใช้ผ่านการเปลี่ยนหน้า

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ทุกครั้งที่ประสบการณ์ของผู้ใช้ถูกขัดจังหวะ โอกาสที่พวกเขาจะออกจะเพิ่มขึ้น การเปลี่ยนจากหน้าหนึ่งไปยังอีกหน้าหนึ่งมักจะทำให้เกิดการหยุดชะงักโดยการแสดงแสงสีขาวที่ไม่มีเนื้อหา โดยใช้เวลานานเกินไปในการโหลดหรือนำผู้ใช้ออกจากบริบทก่อนที่หน้าใหม่จะเปิดขึ้น

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

Page Transitions

ในบทความนี้ เราจะสร้างการเปลี่ยนแปลงระหว่างหน้าทีละขั้นตอน เราจะพูดถึงข้อดีและข้อเสียของเทคนิคนี้และวิธีผลักดันให้ถึงขีดจำกัด

ตัวอย่าง

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

ทำไมเราไม่ใช้วิธีเดียวกันกับเว็บไซต์ของเรา? เหตุใดเราจึงพอใจกับผู้ใช้ที่รู้สึกว่าถูกเทเลพอร์ตทุกครั้งที่มีการเปลี่ยนแปลงหน้า

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

วิธีเปลี่ยนระหว่างหน้าเว็บ

กรอบงานสปา

ก่อนที่จะทำให้มือของเราสกปรก ฉันควรพูดอะไรบางอย่างเกี่ยวกับเฟรมเวิร์กแอปพลิเคชันหน้าเดียว (SPA) หากคุณใช้เฟรมเวิร์ก SPA (เช่น AngularJS, Backbone.js หรือ Ember) การสร้างทรานซิชันระหว่างหน้าต่างๆ จะง่ายขึ้นมาก เนื่องจาก JavaScript จัดการการกำหนดเส้นทางทั้งหมดแล้ว โปรดดูเอกสารที่เกี่ยวข้องเพื่อดูวิธีการเปลี่ยนหน้าโดยใช้กรอบงานที่คุณเลือก เนื่องจากอาจมีตัวอย่างและบทช่วยสอนที่ดี

ทางที่ผิด

ความพยายามครั้งแรกของฉันในการสร้างการเปลี่ยนแปลงระหว่างหน้าต่างๆ มีลักษณะดังนี้:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

แนวคิดนั้นเรียบง่าย: ใช้แอนิเมชั่นหนึ่งอันเมื่อผู้ใช้ออกจากเพจ และใช้แอนิเมชั่นอื่นเมื่อเพจใหม่โหลดขึ้น

อย่างไรก็ตาม ในไม่ช้าฉันก็พบว่าโซลูชันนี้มีข้อจำกัดบางประการ:

  • เราไม่รู้ว่าหน้าถัดไปจะโหลดนานแค่ไหน ดังนั้นภาพเคลื่อนไหวอาจดูไม่ลื่นไหล
  • เราไม่สามารถสร้างการเปลี่ยนที่รวมเนื้อหาจากหน้าก่อนหน้าและหน้าถัดไป

อันที่จริง วิธีเดียวที่จะบรรลุการเปลี่ยนแปลงที่ราบรื่นและราบรื่นคือการควบคุมกระบวนการเปลี่ยนหน้าอย่างสมบูรณ์ ดังนั้นจึง ไม่ต้องเปลี่ยนหน้า เลย จึงต้องเปลี่ยนแนวทางแก้ไขปัญหา

ทางที่ถูก

มาดูขั้นตอนที่เกี่ยวข้องในการสร้างการเปลี่ยนข้ามหน้าอย่างง่ายระหว่างหน้าอย่างถูกวิธี มันเกี่ยวข้องกับสิ่งที่เรียกว่าการนำทาง pushState AJAX (หรือ PJAX) ซึ่งจะทำให้เว็บไซต์ของเรากลายเป็นเว็บไซต์แบบหน้าเดียว

เทคนิคนี้ไม่เพียงแต่บรรลุการเปลี่ยนแปลงที่ราบรื่นและน่าพึงพอใจ แต่เราจะได้รับประโยชน์จากข้อดีอื่นๆ ซึ่งจะกล่าวถึงในรายละเอียดในบทความนี้ต่อไป

ป้องกันพฤติกรรมลิงก์เริ่มต้น

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

 // Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });

วิธีการเพิ่มตัวฟังเหตุการณ์ไปยังองค์ประกอบหลัก แทนที่จะเพิ่มลงในแต่ละโหนด เรียกว่าการมอบหมายเหตุการณ์ และเป็นไปได้เนื่องจากลักษณะเหตุการณ์ที่เกิดฟองสบู่ของ HTML DOM API

ดึงหน้า

ตอนนี้เราได้ขัดจังหวะเบราว์เซอร์เมื่อพยายามเปลี่ยนหน้า เราสามารถดึงหน้านั้นด้วยตนเองโดยใช้ Fetch API ลองดูที่ฟังก์ชันต่อไปนี้ ซึ่งจะดึงเนื้อหา HTML ของหน้าเมื่อกำหนด URL

 function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }

สำหรับเบราว์เซอร์ที่ไม่รองรับ Fetch API ให้ลองเพิ่ม polyfill หรือใช้ XMLHttpRequest ที่ล้าสมัย

เปลี่ยน URL ปัจจุบัน

HTML5 มี API ที่ยอดเยี่ยมที่เรียกว่า pushState ซึ่งช่วยให้เว็บไซต์สามารถเข้าถึงและแก้ไขประวัติของเบราว์เซอร์โดยไม่ต้องโหลดหน้าใดๆ ด้านล่างนี้ เรากำลังใช้เพื่อแก้ไข URL ปัจจุบันให้เป็น URL ของหน้าถัดไป โปรดทราบว่านี่เป็นการแก้ไขตัวจัดการเหตุการณ์การคลิกของจุดยึดที่เราประกาศไว้ก่อนหน้านี้

 if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }

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

 window.addEventListener('popstate', changePage);

จากทั้งหมดนี้ เรากำลังสร้างระบบการกำหนดเส้นทางแบบดั้งเดิม ซึ่งเรามีโหมดแอ็คทีฟและพาสซีฟ

โหมดแอคทีฟของเราถูกใช้งานเมื่อผู้ใช้คลิกที่ลิงค์และเราเปลี่ยน URL โดยใช้ pushState ในขณะที่โหมดพาสซีฟถูกใช้เมื่อ URL เปลี่ยนไปและเราได้รับแจ้งจากเหตุการณ์ popstate สเตต ไม่ว่าในกรณีใด เราจะเรียก changePage ซึ่งดูแลการอ่าน URL ใหม่และโหลดหน้าที่เกี่ยวข้อง

แยกวิเคราะห์และเพิ่มเนื้อหาใหม่

โดยปกติ หน้าที่นำทางจะมีองค์ประกอบทั่วไป เช่น header และ footer สมมติว่าเราใช้โครงสร้าง DOM ต่อไปนี้บนหน้าเว็บทั้งหมดของเรา (ซึ่งจริงๆ แล้วเป็นโครงสร้างของ Smashing Magazine):

เคลื่อนไหว!

เมื่อผู้ใช้คลิกลิงก์ ฟังก์ชัน changePage จะ ดึงข้อมูล HTML ของหน้านั้น จาก นั้นแตกไฟล์ cc และ เพิ่ม ลงในองค์ประกอบ main ณ จุดนี้ เรามีคอนเทนเนอร์ cc สองอันในหน้าของเรา อันแรกเป็นของเพจก่อนหน้า และอันที่สองจากหน้าถัดไป

ฟังก์ชันถัดไป animate จะดูแล crossfading คอนเทนเนอร์ทั้งสองโดยการซ้อนทับกัน ทำให้คอนเทนเนอร์เก่าจางลง เฟดในคอนเทนเนอร์ใหม่ และนำคอนเทนเนอร์เก่าออก ในตัวอย่างนี้ ฉันใช้ Web Animations API เพื่อสร้างแอนิเมชั่นเฟด แต่แน่นอนว่าคุณสามารถใช้เทคนิคหรือไลบรารีใดก็ได้ที่คุณต้องการ

 function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }

รหัสสุดท้ายมีอยู่ใน GitHub

และนั่นคือพื้นฐานของการเปลี่ยนหน้าเว็บ!

คำเตือนและข้อจำกัด

ตัวอย่างเล็กๆ น้อยๆ ที่เราเพิ่งสร้างขึ้นนั้นยังห่างไกลจากความสมบูรณ์แบบ อันที่จริง เรายังไม่ได้พิจารณาบางสิ่ง:

  • ตรวจสอบให้แน่ใจว่าเรามีผลกับลิงก์ที่ถูกต้อง
    ก่อนเปลี่ยนลักษณะการทำงานของลิงก์ เราควรเพิ่มการตรวจสอบเพื่อให้แน่ใจว่าควรเปลี่ยน ตัวอย่างเช่น เราควรละเว้นลิงก์ทั้งหมดที่มี target="_blank" (ซึ่งเปิดหน้าในแท็บใหม่) ลิงก์ทั้งหมดไปยังโดเมนภายนอก และกรณีพิเศษอื่นๆ เช่น Control/Command + click (ซึ่งจะเปิดหน้าใน แท็บใหม่)
  • อัปเดตองค์ประกอบภายนอกคอนเทนเนอร์เนื้อหาหลัก
    ปัจจุบัน เมื่อหน้าเปลี่ยนแปลง องค์ประกอบทั้งหมดภายนอกคอนเทนเนอร์ cc จะยังคงเหมือนเดิม อย่างไรก็ตาม องค์ประกอบเหล่านี้บางส่วนจะต้องมีการเปลี่ยนแปลง (ซึ่งขณะนี้สามารถทำได้ด้วยตนเองเท่านั้น) รวมถึง title เอกสาร องค์ประกอบเมนูที่มีคลาสที่ active และอื่นๆ อีกมากมายอาจขึ้นอยู่กับเว็บไซต์
  • จัดการวงจรชีวิตของ JavaScript
    หน้าของเราตอนนี้ทำงานเหมือน SPA ซึ่งเบราว์เซอร์จะไม่เปลี่ยนหน้าเอง ดังนั้น เราจึงต้องดูแลวงจรชีวิตของ JavaScript ด้วยตนเอง ตัวอย่างเช่น การผูกและไม่ผูกเหตุการณ์บางอย่าง การประเมินปลั๊กอินอีกครั้ง และรวมถึงโพลีฟิลและโค้ดของบุคคลที่สาม

รองรับเบราว์เซอร์

ข้อกำหนดเพียงอย่างเดียวสำหรับโหมดการนำทางที่เราใช้นี้คือ pushState API ซึ่งมีอยู่ในเบราว์เซอร์รุ่นใหม่ทั้งหมด เทคนิคนี้ ทำงานอย่างเต็มที่เป็นการเพิ่มประสิทธิภาพแบบก้าวหน้า เพจยังคงให้บริการและเข้าถึงได้ตามปกติ และเว็บไซต์จะยังคงทำงานได้ตามปกติเมื่อปิดการใช้งาน JavaScript

หากคุณกำลังใช้เฟรมเวิร์ก SPA ให้พิจารณาใช้การนำทาง PJAX แทน เพื่อให้การนำทางเป็นไปอย่างรวดเร็ว ในการทำเช่นนั้น คุณจะได้รับการสนับสนุนแบบเดิมและสร้างเว็บไซต์ที่เป็นมิตรกับ SEO มากขึ้น

ก้าวต่อไป

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

การใช้แคช

ด้วยการเปลี่ยนฟังก์ชัน loadPage เล็กน้อย เราสามารถเพิ่มแคชอย่างง่าย ซึ่งทำให้แน่ใจว่าหน้าที่เข้าชมแล้วจะไม่ถูกโหลดซ้ำ

 var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }

ตามที่คุณอาจเดาได้ เราสามารถใช้แคชแบบถาวรมากขึ้นกับ Cache API หรือแคชที่เก็บข้อมูลถาวรฝั่งไคลเอ็นต์อื่น (เช่น IndexedDB)

ทำให้หน้าปัจจุบันเคลื่อนไหว

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

การใช้คำสัญญา การจัดการกับสถานการณ์แบบนี้กลายเป็นเรื่องง่าย เมธอด .all จะสร้างคำสัญญาใหม่ที่ได้รับการแก้ไขทันทีที่คำสัญญาทั้งหมดที่รวมเป็นข้อโต้แย้งได้รับการแก้ไข

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

กำลังดึงหน้าถัดไปล่วงหน้า

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

อย่างไรก็ตาม เราสามารถไปได้ไกลยิ่งขึ้นโดยเริ่มโหลดหน้าถัดไปล่วงหน้าเมื่อผู้ใช้วางเมาส์เหนือหรือเริ่มแตะลิงก์

อย่างที่คุณเห็น โดยปกติจะมีความล่าช้า 200 ถึง 300 มิลลิวินาทีในการวางเมาส์และคลิกของผู้ใช้ นี้เป็น เวลาตาย และมักจะเพียงพอที่จะโหลดหน้าถัดไป

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

อีกปัจจัยที่เราสามารถตรวจจับและพิจารณาในการตัดสินใจว่าจะดึงข้อมูลล่วงหน้าหรือไม่คือความเร็วในการเชื่อมต่อของผู้ใช้ (อาจจะเป็นไปได้ในอนาคตด้วย Network Information API)

เอาต์พุตบางส่วน

ในฟังก์ชัน loadPage ของเรา เรากำลังดึงเอกสาร HTML ทั้งหมด แต่จริงๆ แล้วเราต้องการแค่คอนเทนเนอร์ cc หากเราใช้ภาษาฝั่งเซิร์ฟเวอร์ เราสามารถตรวจจับได้ว่าคำขอนั้นมาจากการเรียก AJAX ที่กำหนดเองหรือไม่ และหากเป็นเช่นนั้น ให้ส่งออกเฉพาะคอนเทนเนอร์ที่ต้องการ ด้วยการใช้ Headers API เราสามารถส่งส่วนหัว HTTP ที่กำหนดเองในคำขอดึงข้อมูลของเราได้

 function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }

จากนั้น ทางฝั่งเซิร์ฟเวอร์ (โดยใช้ PHP ในกรณีนี้) เราสามารถตรวจพบว่ามีส่วนหัวที่กำหนดเองของเราหรือไม่ก่อนที่จะส่งออกเฉพาะคอนเทนเนอร์ที่จำเป็น:

 if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }

สิ่งนี้จะลดขนาดของข้อความ HTTP และยังช่วยลดการโหลดฝั่งเซิร์ฟเวอร์

ห่อ

หลังจากใช้เทคนิคนี้ในสองโปรเจ็กต์ ฉันตระหนักว่าไลบรารีที่นำกลับมาใช้ใหม่ได้จะมีประโยชน์อย่างมาก จะช่วยประหยัดเวลาในการใช้งานในแต่ละโอกาส ทำให้ผมมีอิสระที่จะมุ่งความสนใจไปที่เอฟเฟกต์การเปลี่ยนแปลงด้วยตนเอง

ด้วยเหตุนี้ Barba.js จึงถือกำเนิดขึ้น ซึ่งเป็นไลบรารีขนาดเล็ก (ลดขนาด 4 KB และ gZip'd) ที่สรุปความซับซ้อนทั้งหมดออกไป และมอบ API ที่ดี สะอาด และเรียบง่ายสำหรับนักพัฒนาที่จะใช้ นอกจากนี้ยังพิจารณามุมมองและมาพร้อมกับการเปลี่ยน แคช การดึงข้อมูลล่วงหน้า และเหตุการณ์ที่นำมาใช้ซ้ำได้ เป็นโอเพ่นซอร์สและพร้อมใช้งานบน GitHub

บทสรุป

ตอนนี้เราได้เห็นวิธีการสร้างเอฟเฟกต์ crossfade และข้อดีและข้อเสียของการใช้การนำทาง PJAX เพื่อเปลี่ยนเว็บไซต์ของเราให้เป็น SPA อย่างมีประสิทธิภาพ นอกเหนือจากประโยชน์ของการเปลี่ยนแปลงแล้ว เรายังได้เห็นวิธีการใช้กลไกการแคชและการดึงข้อมูลล่วงหน้าอย่างง่ายเพื่อเพิ่มความเร็วในการโหลดหน้าใหม่

บทความทั้งหมดนี้อิงจากประสบการณ์ส่วนตัวของฉันและสิ่งที่ฉันได้เรียนรู้จากการใช้การเปลี่ยนหน้าในโครงการที่ฉันได้ทำ หากคุณมีคำถามใด ๆ อย่าลังเลที่จะแสดงความคิดเห็นหรือติดต่อเราทาง Twitter - ข้อมูลของฉันอยู่ด้านล่าง!

อ่านเพิ่มเติม เกี่ยวกับ SmashingMag:

  • การเปลี่ยนผ่านอย่างชาญฉลาดในการออกแบบประสบการณ์ผู้ใช้
  • การออกแบบในช่วงเปลี่ยนผ่านสู่โลกแห่งอุปกรณ์ที่หลากหลาย
  • มอบประสบการณ์ดั้งเดิมด้วยเทคโนโลยีเว็บ