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

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

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

สรุปสั้นๆ

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

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

พารัลแลกซ์ปัญหา

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

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

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

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

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

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

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

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

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

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

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

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

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

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

position: sticky ช่วยคุณได้

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

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

การใช้ 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);
}

ซึ่งจะเป็นการคืนค่าเอฟเฟกต์พารัลแลกซ์สำหรับ Mobile Safari ซึ่งเป็นข่าวที่ยอดเยี่ยมตั้งแต่ต้น!

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

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

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

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

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

การสาธิตโดย Robert Flack แสดงให้เห็นว่า position: sticky ส่งผลต่อการเลื่อนพารัลแลกซ์อย่างไร

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

แต่ก็ยังมีก้อนและตุ่มที่จำเป็นต้องได้รับการปรับให้เรียบเสมอ ซึ่งมีดังนี้

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

บทสรุป

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

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