พารัลแลกซ์สำหรับการแสดง

พอล ลูอิส
โรเบิร์ต แฟล็ก
Robert Flack

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

ภาพพารัลแลกซ์

สรุป

  • อย่าใช้เหตุการณ์การเลื่อนหรือbackground-positionเพื่อสร้างภาพเคลื่อนไหวแบบพารัลแลกซ์
  • ใช้การแปลงแบบ 3 มิติของ CSS เพื่อสร้างเอฟเฟกต์พารัลแลกซ์ที่แม่นยำยิ่งขึ้น
  • สำหรับ Safari บนอุปกรณ์เคลื่อนที่ ให้ใช้ position: sticky เพื่อให้ระบบเผยแพร่เอฟเฟกต์พารัลแลกซ์

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

พารัลแลกซ์ที่มีปัญหา

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

ไม่ดี: ใช้เหตุการณ์การเลื่อน

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

ข้อมูลสำคัญนี้จะบอกให้เราทราบว่าเพราะเหตุใดเราจึงต้องหลีกเลี่ยงโซลูชันที่ใช้ JavaScript ซึ่งย้ายองค์ประกอบตามเหตุการณ์การเลื่อน JavaScript ไม่ได้รับประกันว่าการพารัลแลกซ์จะสอดคล้องกับตำแหน่งการเลื่อนของหน้าเว็บ ใน Mobile Safari เวอร์ชันเก่า เหตุการณ์การเลื่อนจะปรากฏที่ตอนท้ายของการเลื่อน ซึ่งทำให้ไม่สามารถสร้างเอฟเฟกต์การเลื่อนที่ใช้ JavaScript ได้ เวอร์ชันล่าสุดจะแสดงเหตุการณ์การเลื่อนระหว่างภาพเคลื่อนไหว แต่ในทำนองเดียวกับ Chrome คือ "พยายามอย่างเต็มที่" หากเทรดหลักกำลังยุ่งอยู่กับงานอื่น ระบบจะไม่แสดงเหตุการณ์การเลื่อนทันที ซึ่งหมายความว่าเอฟเฟกต์พารัลแลกซ์จะหายไป

ไม่ดี: กำลังอัปเดต background-position

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

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

CSS ในแบบ 3 มิติ

ทั้ง Scott Kellum และ Keith Clark ต่างทำงานสำคัญในด้านการใช้ CSS 3D เพื่อให้เกิดการเคลื่อนไหวพารัลแลกซ์ และเทคนิคที่ใช้อย่างมีประสิทธิภาพมีดังนี้

  • ตั้งค่าองค์ประกอบที่มีเพื่อเลื่อนดูด้วย overflow-y: scroll (และอาจเป็น overflow-x: hidden)
  • สำหรับองค์ประกอบเดียวกันนั้น ให้ใช้ค่า perspective และ perspective-origin ซึ่งตั้งค่าเป็น top left หรือ 0 0
  • ในส่วนย่อยขององค์ประกอบนั้นจะใช้คำแปลใน Z และปรับขนาดกลับขึ้นมาเพื่อให้พารัลแลกซ์เคลื่อนไหวโดยไม่ส่งผลกระทบต่อขนาดบนหน้าจอ

CSS สำหรับวิธีการนี้มีลักษณะดังนี้

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

ซึ่งสมมติว่าข้อมูลโค้ด HTML มีลักษณะดังนี้

<div class="container">
    <div class="parallax-child"></div>
</div>

การปรับขนาดสำหรับมุมมอง

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

ในกรณีของโค้ดข้างต้น มุมมองคือ 1px และระยะ Z ของ parallax-child คือ -2px ซึ่งหมายความว่าองค์ประกอบจะต้องเพิ่มขนาดขึ้น 3x ซึ่งคุณจะเห็นค่าที่ใส่ในโค้ด ดังนี้ scale(3)

สำหรับเนื้อหาที่ไม่ได้ใช้ค่า translateZ คุณแทนที่ค่า 0 ได้ หมายความว่าสเกลคือ (perspective - 0) / perspective ซึ่งคิดเป็นค่า 1 ซึ่งหมายความว่ามีการขยายขนาด ไม่ใช่ขึ้นหรือลง มีประโยชน์มากจริงๆ

วิธีการทำงาน

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

อย่างไรก็ตาม การนำค่ามุมมองไปใช้กับองค์ประกอบแบบเลื่อนจะยุ่งเหยิงกับกระบวนการนี้ ซึ่งจะเปลี่ยนเมทริกซ์ที่สนับสนุนการเปลี่ยนรูปแบบการเลื่อน ตอนนี้การเลื่อน 300px อาจเลื่อนลูกๆ ไป 150px เท่านั้น ซึ่งขึ้นอยู่กับค่า perspective และ translateZ ที่คุณเลือกไว้ หากองค์ประกอบมีค่า translateZ เป็น 0 องค์ประกอบจะเลื่อนที่ 1:1 (ตามที่เคย) แต่เด็กที่พุชใน Z ออกห่างจากต้นทางของมุมมองจะเลื่อนด้วยอัตราที่ต่างกัน ผลลัพธ์สุทธิ: การเคลื่อนไหวแบบพารัลแลกซ์ และที่สำคัญมากๆ คือฟังก์ชันนี้เป็นส่วนหนึ่งของกลไกการเลื่อนภายในของเบราว์เซอร์โดยอัตโนมัติ ซึ่งหมายความว่าไม่จำเป็นต้องรอฟังเหตุการณ์ scroll หรือเปลี่ยนแปลง background-position

แมลงวันในขี้ผึ้ง: Mobile Safari

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

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

ใน HTML ข้างต้น .parallax-container เป็นโค้ดใหม่และจะแยกค่า perspective อย่างมีประสิทธิภาพ ซึ่งจะทำให้เราสูญเสียเอฟเฟกต์พารัลแลกซ์ ในกรณีส่วนใหญ่ วิธีแก้ปัญหาก็ค่อนข้างตรงไปตรงมา โดยการเพิ่ม transform-style: preserve-3d ลงในองค์ประกอบ ทำให้เอฟเฟกต์ 3 มิติเผยแพร่เอฟเฟกต์ 3 มิติ (เช่น ค่ามุมมองของเรา) ที่มีการนำไปใช้ต่อในต้นไม้

.parallax-container {
  transform-style: preserve-3d;
}

แต่ในกรณีของ Mobile Safari อาจมีความซับซ้อนมากขึ้นเล็กน้อย การใช้ overflow-y: scroll กับองค์ประกอบคอนเทนเนอร์ใช้งานได้ทางเทคนิค แต่จะต้องปัดองค์ประกอบแบบเลื่อนได้ วิธีแก้ปัญหาคือให้เพิ่ม -webkit-overflow-scrolling: touch แต่จะเป็นการรวม perspective ไว้ด้วย และเราจะไม่ได้รับพารัลแลกซ์

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

position: sticky มาช่วยแล้ว!

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

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

การใช้ position: -webkit-sticky กับองค์ประกอบพารัลแลกซ์ช่วยให้เรา "ย้อนกลับ" ผลกระทบจากการรวม -webkit-overflow-scrolling: touch ได้อย่างมีประสิทธิภาพ วิธีนี้ช่วยให้มั่นใจได้ว่าองค์ประกอบพารัลแลกซ์จะอ้างอิงระดับบนที่ใกล้ที่สุดด้วยกล่องเลื่อน ซึ่งในกรณีนี้คือ .container จากนั้น ในทำนองเดียวกับก่อนหน้านี้ .parallax-container จะใช้ค่า perspective ซึ่งจะเปลี่ยนออฟเซ็ตการเลื่อนที่คำนวณแล้วและสร้างเอฟเฟกต์พารัลแลกซ์

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

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

คำเตือนเกี่ยวกับการกำหนดตำแหน่งติดหนึบ

แต่มีความแตกต่างตรงนี้คือ position: sticky เปลี่ยนกลไกของพารัลแลกซ์ ตำแหน่งแบบติดหนึบจะพยายามยึดองค์ประกอบนั้นไว้กับคอนเทนเนอร์แบบเลื่อน ขณะที่เวอร์ชันที่ไม่ติดหนึบจะไม่สามารถทำได้ ซึ่งหมายความว่าพารัลแลกซ์ที่มีโฆษณาที่มีติดหนึบจะผกผันกับพารัลแลกซ์ที่ไม่มี

  • เมื่อใช้ position: sticky องค์ประกอบที่อยู่ใกล้กับ z=0 ก็จะเคลื่อนที่น้อยลง
  • หากไม่มี position: sticky องค์ประกอบที่ใกล้ก็จะเคลื่อนที่ z=0 มากขึ้น

หากทั้งหมดดูไม่ออกอย่างที่ต้องการ ให้ลองดูการสาธิตนี้ของ Robert Flack ที่แสดงให้เห็นว่าองค์ประกอบมีลักษณะการทำงานที่ต่างกันอย่างไรเมื่อมีและไม่มีการกำหนดตำแหน่งแบบติดหนึบ หากต้องการดูความแตกต่าง คุณต้องใช้ Chrome Canary (ซึ่งเป็นเวอร์ชัน 56 ในเวลาที่เขียน) หรือ Safari

ภาพหน้าจอมุมมองพารัลแลกซ์

การสาธิตของ Robert Flack ที่แสดงให้เห็นผลกระทบของ position: sticky ที่มีต่อการเลื่อนพารัลแลกซ์

ข้อบกพร่องต่างๆ และวิธีแก้ไขปัญหาชั่วคราว

แต่ก็ยังมีตุ่มและตุ่มๆ ที่ต้องทำให้เรียบเนียน ดังนี้

  • การสนับสนุนมีความเสถียรไม่สม่ำเสมอ ระบบยังคงสนับสนุนใน Chrome, Edge ยังขาดการสนับสนุนทั้งหมด และ Firefox มีข้อบกพร่องภาพสีเมื่อผสาน Sticky เข้ากับการเปลี่ยนรูปแบบมุมมอง ในกรณีเช่นนี้ ขอแนะนำให้เพิ่มโค้ดสั้นๆ เพื่อเพิ่ม position: sticky (เวอร์ชันที่นำหน้าด้วย -webkit-) เท่านั้นเมื่อต้องการ ซึ่งใช้กับ Mobile Safari เท่านั้น
  • เอฟเฟกต์นี้ไม่ได้ "ได้ผล" ใน Edge Edge พยายามจัดการกับการเลื่อนในระดับระบบปฏิบัติการ ซึ่งโดยทั่วไปก็เป็นเรื่องดี แต่ในกรณีนี้จะป้องกันไม่ให้มีการตรวจจับการเปลี่ยนแปลงมุมมองระหว่างการเลื่อน หากต้องการแก้ไขปัญหานี้ คุณสามารถเพิ่มองค์ประกอบที่มีตำแหน่งคงที่ได้ เนื่องจากวิธีนี้เป็นการเปลี่ยน Edge ไปเป็น วิธีการเลื่อนที่ไม่ใช่ระบบปฏิบัติการ และช่วยให้มั่นใจว่าองค์ประกอบดังกล่าวคำนึงถึงการเปลี่ยนแปลงของมุมมอง
  • "หน้าเว็บมีเนื้อหามากมายมหาศาล" หลายๆ เบราว์เซอร์คำนึงถึงขนาด เมื่อต้องตัดสินใจว่าเนื้อหาของหน้าเว็บจะใหญ่แค่ไหน แต่ Chrome และ Safari ไม่คำนึงถึงมุมมอง ดังนั้น ถ้ามี เช่น ใช้สเกล 3x กับองค์ประกอบ คุณอาจเห็นแถบเลื่อนและสิ่งที่คล้ายกันนี้ แม้ว่าองค์ประกอบจะอยู่ที่ 1x หลังจากใช้ perspective แล้วก็ตาม คุณแก้ปัญหานี้ได้โดยการปรับขนาดองค์ประกอบจากมุมขวาล่าง (ที่มี transform-origin: bottom right) ซึ่งทำงานได้เพราะจะทำให้องค์ประกอบที่มีขนาดใหญ่เกินไปกลายเป็น "พื้นที่เชิงลบ" (โดยทั่วไปคือด้านบนซ้าย) ของพื้นที่ที่เลื่อนได้ ส่วนพื้นที่ที่เลื่อนได้จะไม่ทำให้คุณเห็นหรือเลื่อนไปยังเนื้อหาในพื้นที่เชิงลบ

บทสรุป

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

ลองเล่นเกมแล้วบอกเราว่าคุณเป็นอย่างไรบ้าง