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