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

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

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

สรุป

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

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

ปัญหาเกี่ยวกับภาพพารัลแลกซ์

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

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

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

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

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

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

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

CSS ในแบบ 3 มิติ

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

  • ตั้งค่าองค์ประกอบที่บรรจุเพื่อเลื่อนด้วย 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 - distance) / perspective เนื่องจากเราต้องการมากที่สุดคือการให้องค์ประกอบภาพพารัลแลกซ์แสดงผลแบบพารัลแลกซ์แต่ปรากฏในขนาดที่เราเขียนขึ้น องค์ประกอบดังกล่าวจึงต้องปรับขนาดขึ้นด้วยวิธีนี้แทนที่จะปล่อยไว้ตามเดิม

ในกรณีของโค้ดด้านบน มุมมองคือ 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

ข้อเสีย: 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;
}

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

บทสรุป

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

ลองใช้แล้วแจ้งให้เราทราบ