ไม่ว่าจะชอบหรือไม่ชอบ พารัลแลกซ์ก็อยู่ที่นี่ตลอดไป การใช้อย่างมีวิจารณญาณจะเพิ่มความลึกและความละเอียดให้กับเว็บแอปได้ อย่างไรก็ตาม ปัญหาอยู่ที่การใช้พารัลแลกซ์อย่างมีประสิทธิภาพอาจเป็นเรื่องท้าทาย ในบทความนี้ เราจะพูดถึงโซลูชันที่ทั้งมีประสิทธิภาพและที่สำคัญ ยิ่งไปกว่านั้นคือโซลูชันที่ทำงานข้ามเบราว์เซอร์ได้
![ภาพพารัลแลกซ์](https://developer.chrome.com/static/blog/performant-parallaxing/image/parallax-illustration-330a26c14e2fc.jpg?authuser=5&hl=th)
สรุปสั้นๆ
- อย่าใช้เหตุการณ์แบบเลื่อนหรือ
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
![ภาพหน้าจอแบบมุมมองพารัลแลกซ์](https://developer.chrome.com/static/blog/performant-parallaxing/image/parallax-perspective-scre-e5a0b46e4d2ad.jpg?authuser=5&hl=th)
การสาธิตโดย 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
มาสนุกกันและบอกเราว่าคุณเป็นอย่างไรบ้าง