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

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

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

สรุป

  • อย่าใช้เหตุการณ์การเลื่อนหรือ 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

โลดแล่นบนขี้ผึ้ง: 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;
}

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

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