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

ในบทความนี้ เราจะสร้างการเปลี่ยนแปลงระหว่างหน้าทีละขั้นตอน เราจะพูดถึงข้อดีและข้อเสียของเทคนิคนี้และวิธีผลักดันให้ถึงขีดจำกัด
ตัวอย่าง
แอพมือถือจำนวนมากใช้ประโยชน์จากการเปลี่ยนระหว่างมุมมองต่างๆ ในตัวอย่างด้านล่าง ซึ่งเป็นไปตามหลักเกณฑ์การออกแบบวัสดุของ 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):
var main = document.querySelector('main'); function changePage() { // Note, the URL has already been changed var url = window.location.href; loadPage(url).then(function(responseText) { var wrapper = document.createElement('div'); wrapper.innerHTML = responseText; var oldContent = document.querySelector('.cc'); var newContent = wrapper.querySelector('.cc'); main.appendChild(newContent); animate(oldContent, newContent); }); }
เคลื่อนไหว!
เมื่อผู้ใช้คลิกลิงก์ ฟังก์ชัน 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:
- การเปลี่ยนผ่านอย่างชาญฉลาดในการออกแบบประสบการณ์ผู้ใช้
- การออกแบบในช่วงเปลี่ยนผ่านสู่โลกแห่งอุปกรณ์ที่หลากหลาย
- มอบประสบการณ์ดั้งเดิมด้วยเทคโนโลยีเว็บ