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

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

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

สรุปสั้นๆ

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

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

ผู้ที่ชอบแก้ปัญหา

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

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

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

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

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

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

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

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 ซึ่งหมายความว่าสเกลคือ (มุมมอง - 0) / มุมมอง ซึ่งจะให้ค่าเป็น 1 ซึ่งหมายความว่าไม่ได้ปรับขนาด ขึ้นหรือลง สะดวกมากจริงๆ

วิธีการทำงานของแนวทางนี้

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

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

ข้อควรทราบ: Safari บนอุปกรณ์เคลื่อนที่

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

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

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

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

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

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

บทสรุป

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

ลองใช้ฟีเจอร์นี้ แล้วบอกให้เราทราบว่าคุณคิดเห็นอย่างไร