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

เกริ่นนำ

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

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

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

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

การทำให้เว็บไซต์พารัลแลกซ์มีข้อสรุปเช่นนี้เป็นสิ่งที่สมเหตุสมผล

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

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

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

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

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

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

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

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

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

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

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

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

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

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

อีกแนวทางหนึ่งที่เราทำได้แทนที่จะใช้ตำแหน่งสัมบูรณ์คือการใช้การแปลงแบบ 3 มิติกับองค์ประกอบ ในกรณีนี้เราจะเห็นว่าองค์ประกอบที่ใช้การแปลงแบบ 3D จะมีเลเยอร์ใหม่ต่อองค์ประกอบ และในเบราว์เซอร์ 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

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

  • เราไม่ต้องใช้ตัวจัดวางองค์ประกอบมากนักอีกต่อไป เพราะมีเพียงองค์ประกอบเดียว นั่นก็คือ 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));
}

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

เมื่อเราได้ตรวจสอบให้ไกลที่สุดแล้วก็ไม่มีเหตุผลที่จะสันนิษฐานได้ว่าควรทำงานพารัลแลกซ์ภายในองค์ประกอบ 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 สร้างเลเยอร์ไม่จำเป็นต้องสัมพันธ์กับเครื่องมืออื่นๆ ของเบราว์เซอร์ ดังนั้น โปรดทดสอบเบราว์เซอร์ก่อนที่จะลองใช้โซลูชันดังกล่าว

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

บทสรุป

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

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