พารัลแลกซิน'

บทนำ

เว็บไซต์ภาพพารัลแลกซ์กำลังมาแรงในช่วงนี้ โปรดดูตัวอย่างต่อไปนี้

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

หน้าพารัลแลกซ์สาธิต
หน้าเดโมของเราพร้อมเอฟเฟกต์พารัลแลกซ์

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

ตัวอย่างเว็บไซต์ภาพพารัลแลกซ์ที่อาจพบได้ทั่วไปมีดังนี้

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

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

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

ตัวเลือกที่ 1: ใช้องค์ประกอบ DOM และตำแหน่งสัมบูรณ์

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

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

เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ที่ไม่มีเหตุการณ์การเลื่อนที่มีการหน่วงเวลา
เครื่องมือสำหรับนักพัฒนาเว็บแสดงการวาดภาพขนาดใหญ่และเลย์เอาต์ที่ทริกเกอร์เหตุการณ์หลายรายการในเฟรมเดียว

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

มาย้ายโค้ดการอัปเดตออกจากเหตุการณ์การเลื่อนไปยังการเรียกคืน requestAnimationFrame และเพียงบันทึกค่าการเลื่อนในการเรียกคืนของเหตุการณ์การเลื่อน

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

เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ที่มีเหตุการณ์การเลื่อนแบบ debounced
เครื่องมือสำหรับนักพัฒนาเว็บแสดงการวาดภาพขนาดใหญ่และเลย์เอาต์ที่ทริกเกอร์เหตุการณ์หลายรายการในเฟรมเดียว

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

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

ตัวเลือกที่ 2: ใช้องค์ประกอบ DOM และการเปลี่ยนรูปแบบ 3 มิติ

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

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

หลายครั้งที่ผู้คนใช้แฮ็ก -webkit-transform: translateZ(0); และเห็นประสิทธิภาพที่ดีขึ้นอย่างน่าอัศจรรย์ แม้ว่าวิธีนี้จะได้ผลในปัจจุบัน แต่ก็มีปัญหาอยู่

  1. และไม่สามารถใช้กับเบราว์เซอร์ต่างๆ ได้
  2. ซึ่งจะบังคับให้เบราว์เซอร์สร้างเลเยอร์ใหม่สำหรับองค์ประกอบที่เปลี่ยนรูปแบบทุกรายการ เลเยอร์จำนวนมากอาจทำให้เกิดปัญหาคอขวดด้านประสิทธิภาพอื่นๆ ดังนั้นโปรดใช้อย่างประหยัด
  3. ปิดใช้สำหรับพอร์ต WebKit บางรายการ (หัวข้อที่ 4 จากด้านล่าง)

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

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

ตัวเลือกที่ 3: ใช้ Canvas หรือ WebGL ตำแหน่งคงที่

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

  • เราไม่จำเป็นต้องใช้เครื่องมือทำ Composite มากนักอีกต่อไปเนื่องจากมีองค์ประกอบเพียงอย่างเดียวคือ Canvas
  • เราจัดการบิตแมปที่เร่งด้วยฮาร์ดแวร์รายการเดียวได้อย่างมีประสิทธิภาพ
  • Canvas2D API เหมาะอย่างยิ่งกับการเปลี่ยนรูปแบบที่เราต้องการทำ ซึ่งหมายความว่าการพัฒนาและการบำรุงรักษาจะจัดการได้ง่ายขึ้น

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


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

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

เนื่องจากเรากำลังดำเนินการนี้ให้มากที่สุด จึงไม่มีเหตุผลที่จะคิดว่าภาพพารัลแลกซ์ควรทำงานภายในองค์ประกอบ Canvas หากเบราว์เซอร์รองรับ เราจะใช้ WebGL ได้ ประเด็นสำคัญคือ WebGL มีเส้นทางที่ตรงที่สุดไปยังการ์ดกราฟิกสำหรับ API ทั้งหมด จึงมีแนวโน้มที่จะให้ภาพ 60 fps ได้มากที่สุด โดยเฉพาะในกรณีที่เอฟเฟกต์ของเว็บไซต์มีความซับซ้อน

ปฏิกิริยาแรกของคุณอาจคิดว่า WebGL นั้นเกินความจำเป็น หรือการสนับสนุนนั้นยังไม่แพร่หลาย แต่หากคุณใช้ Three.js ก็สามารถเปลี่ยนไปใช้องค์ประกอบ Canvas ได้ทุกเมื่อ และโค้ดของคุณจะได้รับการแยกให้เป็นนามธรรมในลักษณะที่สอดคล้องกันและใช้งานง่าย สิ่งที่ต้องทำมีเพียงใช้ Modernizr เพื่อตรวจสอบการรองรับ API ที่เหมาะสม

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

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

คุณเลือกได้เลย

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

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

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

บทสรุป

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

และเช่นเคย ไม่ว่าคุณจะลองใช้แนวทางใดก็ตาม อย่าเดา ให้ทดสอบ