แถบเลื่อนที่กำหนดเองนั้นหายากมาก และส่วนใหญ่เกิดจากข้อเท็จจริงที่ว่าแถบเลื่อนเป็นหนึ่งในบิตที่เหลืออยู่บนเว็บซึ่งปรับแต่งไม่ได้เลย (ฉันกำลังดูคุณอยู่ เครื่องมือเลือกวันที่) คุณใช้ JavaScript เพื่อสร้างเองได้ แต่วิธีนี้ใช้เงินมาก คุณภาพต่ำ และอาจทำให้รู้สึกหน่วง ในบทความนี้ เราจะใช้ประโยชน์จากเมทริกซ์ CSS ที่แปลกใหม่บางอย่างเพื่อสร้างตัวเลื่อนที่กําหนดเองซึ่งไม่จําเป็นต้องใช้ JavaScript ขณะเลื่อน เพียงแค่โค้ดการตั้งค่าบางส่วน
TL;DR
คุณไม่สนใจเรื่องเล็กๆ น้อยๆ ใช่ไหม คุณแค่ต้องการดูตัวอย่างเพลงของ Nyan Cat และรับคลังเพลงใช่ไหม คุณดูโค้ดของการสาธิตได้ในที่เก็บของ GitHub
LAM;WRA (ยาวและอยู่ในเชิงคณิตศาสตร์ จะอ่านต่อไป)
เมื่อนานมาแล้ว เราสร้างเครื่องมือเลื่อนพารัลแลกซ์ (คุณอ่านบทความนั้นแล้วหรือยัง เนื้อหาดีมาก คุ้มค่ากับเวลาของคุณจริงๆ) การผลักองค์ประกอบกลับโดยใช้การเปลี่ยนรูปแบบ 3 มิติของ CSS ทำให้องค์ประกอบเคลื่อนไหวช้าลงกว่าความเร็วในการเลื่อนจริง
สรุป
มาเริ่มกันด้วยการทบทวนวิธีการทำงานของภาพสไลด์แบบพารัลแลกซ์
ดังที่แสดงในภาพเคลื่อนไหว เราสร้างเอฟเฟกต์ภาพพารัลแลกซ์โดยการผลักองค์ประกอบ "ถอยหลัง" ในอวกาศ 3 มิติตามแนวแกน Z การเลื่อนเอกสารเป็นการเลื่อนตามแนวแกน Y ดังนั้นหากเราเลื่อนลง 100 พิกเซล องค์ประกอบทั้งหมดจะเลื่อนขึ้น 100 พิกเซล ซึ่งมีผลกับองค์ประกอบทั้งหมด แม้แต่องค์ประกอบที่อยู่ "อื่นๆ" แต่เนื่องจากอยู่ไกลจากกล้อง การเคลื่อนไหวบนหน้าจอที่สังเกตได้จะมีขนาดเล็กกว่า 100 พิกเซล ทำให้ได้เอฟเฟ็กต์พารัลแลกซ์ตามที่ต้องการ
แน่นอนว่าการย้ายองค์ประกอบกลับเข้าไปในพื้นที่จะทำให้องค์ประกอบนั้นดูเล็กลงด้วย ซึ่งเราจะแก้ไขโดยการปรับขนาดองค์ประกอบให้กลับมาเหมือนเดิม เราได้หาค่าทางคณิตศาสตร์ที่แน่นอนแล้วเมื่อสร้างแถบเลื่อนแบบพารัลแลกซ์ ดังนั้นเราจะไม่อธิบายรายละเอียดทั้งหมดซ้ำ
ขั้นตอนที่ 0: เราต้องการทําอะไร
แถบเลื่อน นั่นคือสิ่งที่เราจะสร้าง แต่คุณเคยคิดไหมว่าพวกเขาทำอะไร เราไม่แน่ใจ แถบเลื่อนเป็นตัวบ่งชี้ปริมาณเนื้อหาที่มีอยู่ซึ่งแสดงอยู่ในขณะนี้ และความคืบหน้าที่คุณในฐานะผู้อ่านอ่านไปแล้ว หากเลื่อนลง แถบเลื่อนก็จะเลื่อนลงด้วยเพื่อบ่งบอกว่าคุณกำลังดำเนินการใกล้จะเสร็จสิ้น หากเนื้อหาทั้งหมดพอดีกับวิวพอร์ต ระบบมักจะซ่อนแถบเลื่อนไว้ หากเนื้อหามีความสูง 2 เท่าของวิวพอร์ต แถบเลื่อนจะกินพื้นที่ ½ ของความสูงของวิวพอร์ต เนื้อหาที่สูง 3 เท่าของวิวพอร์ตจะปรับแถบเลื่อนให้มีขนาด 1/3 ของวิวพอร์ต เป็นต้น คุณจะเห็นรูปแบบนี้ คุณยังคลิกและลากแถบเลื่อนเพื่อไปยังส่วนต่างๆ ของเว็บไซต์ได้เร็วขึ้นแทนการเลื่อน นี่เป็นลักษณะการทำงานที่น่าประหลาดใจสำหรับองค์ประกอบที่มองไม่เห็นเช่นนี้ มาแก้ปัญหาทีละอย่างกัน
ขั้นตอนที่ 1: เปลี่ยนเป็นโหมดถอยหลัง
โอเค เราทำให้องค์ประกอบเคลื่อนที่ได้ช้ากว่าความเร็วในการเลื่อนด้วยการแปลง CSS 3D ตามที่ระบุไว้ในบทความการเลื่อนพารัลแลกซ์ เราจะกลับทิศทางได้ไหม แต่ปรากฏว่าทำได้ และนี่จึงเป็นวิธีที่เราใช้สร้างแถบเลื่อนที่กำหนดเองซึ่งพอดีกับเฟรม ในการทำความเข้าใจวิธีการทำงานของเทคนิคนี้ เราจำเป็นต้องพูดถึงข้อมูลเบื้องต้นเกี่ยวกับ CSS 3 มิติก่อน
หากต้องการใช้การฉายภาพเชิงมุมในเชิงคณิตศาสตร์ คุณอาจต้องใช้พิกัดแบบสม่ำเสมอ เราจะไม่ลงรายละเอียดว่าพิกัดเหล่านี้คืออะไรและทํางานอย่างไร แต่คุณอาจจํากัดความพิกัดเหล่านี้ได้ว่าเป็นพิกัด 3 มิติที่มีพิกัดที่ 4 เพิ่มเติมที่เรียกว่า w พิกัดนี้ควรเป็น 1 ยกเว้นในกรณีที่คุณต้องการให้มุมมองที่บิดเบี้ยว เราไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดของ w เนื่องจากเราจะไม่ใช้ค่าอื่นนอกเหนือจาก 1 ดังนั้นนับจากนี้ไปจุดทั้งหมดจะเป็นเวกเตอร์ 4 มิติ [x, y, z, w=1] และเมทริกซ์จึงต้องมีขนาด 4x4 ด้วย
กรณีที่คุณจะเห็นว่า CSS ใช้พิกัดแบบสม่ำเสมออยู่เบื้องหลังคือเมื่อคุณกำหนดเมทริกซ์ 4x4 ของคุณเองในพร็อพเพอร์ตี้ transform โดยใช้ฟังก์ชัน matrix3d()
matrix3d
ใช้อาร์กิวเมนต์ 16 รายการ (เนื่องจากเมทริกซ์มีขนาด 4x4) โดยระบุคอลัมน์ทีละคอลัมน์ เราจึงใช้ฟังก์ชันนี้เพื่อระบุการหมุน การแปล ฯลฯ ด้วยตนเองได้ แต่นอกจากนี้ เรายังใช้ฟังก์ชันนี้เพื่อเล่นกับพิกัด w ได้ด้วย
ก่อนที่เราจะใช้ประโยชน์จาก matrix3d()
เราจำเป็นต้องมีบริบทแบบ 3 มิติ เพราะหากไม่มีบริบท 3 มิติ ก็จะไม่มีการบิดเบี้ยวของมุมมองและไม่จำเป็นต้องมีพิกัดแบบเดียวกัน หากต้องการสร้างบริบท 3 มิติ เราต้องใช้คอนเทนเนอร์ที่มี perspective
และองค์ประกอบบางอย่างภายในที่เราเปลี่ยนรูปแบบได้ในอวกาศ 3 มิติที่สร้างขึ้นใหม่ ตัวอย่างเช่น
องค์ประกอบภายในคอนเทนเนอร์ภาพ 3 มิติจะได้รับการประมวลผลโดยเครื่องมือ CSS ดังนี้
- เปลี่ยนแต่ละมุม (จุดยอดมุม) ขององค์ประกอบให้เป็นพิกัดเดียวกัน
[x,y,z,w]
ซึ่งสัมพันธ์กับที่เก็บมุมมอง - ใช้การเปลี่ยนรูปแบบทั้งหมดขององค์ประกอบเป็นเมทริกซ์จากขวาไปซ้าย
- หากเอลิเมนต์ภาพ 3 มิติเลื่อนได้ ให้ใช้เมทริกซ์การเลื่อน
- ใช้เมทริกซ์มุมมอง
เมทริกซ์การเลื่อนคือการเปลี่ยนตำแหน่งตามแกน Y หากเราเลื่อนลง 400 พิกเซล องค์ประกอบทั้งหมดจะต้องเลื่อนขึ้น 400 พิกเซล เมทริกซ์มุมมองคือเมทริกซ์ที่ "ดึง" จุดให้ใกล้กับจุดที่มองไม่เห็นมากขึ้นเมื่อจุดนั้นอยู่ด้านหลังในพื้นที่ 3 มิติ วิธีนี้ทำให้เกิดทั้ง 2 ผลลัพธ์ คือทำให้วัตถุดูเล็กลงเมื่ออยู่ไกลออกไป และทำให้วัตถุ "เคลื่อนไหวช้าลง" เมื่อมีการแปล ดังนั้นหากมีการผลักองค์ประกอบกลับ การเปลี่ยนตำแหน่ง 400 พิกเซลจะทำให้องค์ประกอบดังกล่าวขยับเพียง 300 พิกเซลบนหน้าจอ
หากต้องการทราบรายละเอียดทั้งหมด คุณควรอ่านข้อกำหนดเกี่ยวกับรูปแบบการแสดงผลการเปลี่ยนรูปแบบของ CSS แต่เราอธิบายอัลกอริทึมข้างต้นให้เข้าใจง่ายขึ้นเพื่อประโยชน์ของบทความนี้
กล่องของเราอยู่ภายในคอนเทนเนอร์ภาพมุมมองที่มีค่า p สําหรับแอตทริบิวต์ perspective
และสมมติว่าคอนเทนเนอร์เลื่อนได้และเลื่อนลง 200 พิกเซล
เมทริกซ์แรกคือเมทริกซ์มุมมอง ส่วนเมทริกซ์ที่ 2 คือเมทริกซ์การเลื่อน โดยสรุปแล้ว หน้าที่ของเมทริกซ์การเลื่อนคือทําให้องค์ประกอบเลื่อนขึ้นเมื่อเราเลื่อนลง ด้วยเหตุนี้จึงมีเครื่องหมายลบ
แต่สำหรับแถบเลื่อนของเรา เราต้องการสิ่งตรงข้าม กล่าวคือ เราต้องการให้องค์ประกอบย้ายลงเมื่อเลื่อนลง ตรงนี้เราสามารถใช้เทคนิคในการพลิกพิกัด w ของมุมกล่อง หากพิกัด w เป็น -1 การแปลทั้งหมดจะมีผลในทิศทางตรงกันข้าม เราจะทำได้อย่างไร เครื่องมือ CSS จะจัดการแปลงมุมของกล่องเป็นพิกัดแบบสม่ำเสมอ และตั้งค่า w เป็น 1 ได้เวลาให้ matrix3d()
เฉิดฉายแล้ว
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
เมทริกซ์นี้จะทําเพียงลบล้าง w ดังนั้นเมื่อเครื่องมือ CSS เปลี่ยนแต่ละมุมเป็นเวกเตอร์ของรูปแบบ [x,y,z,1]
เมทริกซ์จะแปลงมุมนั้นเป็น [x,y,z,-1]
เราได้แสดงขั้นตอนกลางเพื่อแสดงผลขององค์ประกอบ transform matrix หากไม่ถนัดคณิตศาสตร์เชิงเมทริกซ์ก็ไม่เป็นไร แนวคิดสำคัญคือในบรรทัดสุดท้าย เราเพิ่มการเลื่อน n ลงในพิกัด y แทนที่จะลบ องค์ประกอบจะเลื่อนลงหากเราเลื่อนลง
อย่างไรก็ตาม หากเราใส่เมทริกซ์นี้ในตัวอย่าง องค์ประกอบจะไม่แสดง เนื่องจากข้อกำหนด CSS กําหนดว่าจุดยอดที่มี w < 0 จะบล็อกไม่ให้องค์ประกอบแสดงผล และเนื่องจากตอนนี้พิกัด z ของเราคือ 0 และ p คือ 1 w จึงเท่ากับ -1
แต่โชคดีที่เราสามารถเลือกค่าของ z ได้ เราต้องตั้งค่า z = -2 เพื่อให้ w = 1
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
ในที่สุดแล้ว กล่องของเราก็กลับมาแล้ว
ขั้นตอนที่ 2: ทําให้ชิ้นงานเคลื่อนไหว
ตอนนี้กล่องของเราก็ปรากฏขึ้นแล้วและมีลักษณะเหมือนเดิมหากไม่มีการเปลี่ยนรูปแบบ ขณะนี้คอนเทนเนอร์ภาพ 3 มิติจะเลื่อนไม่ได้ เราจึงไม่เห็น แต่เราทราบว่าองค์ประกอบจะไปในอีกทิศทางหนึ่งเมื่อเลื่อน มาทำให้คอนเทนเนอร์เลื่อนกัน เราเพียงเพิ่มองค์ประกอบตัวเว้นวรรคที่กินพื้นที่
<div class="container">
<div class="box"></div>
<span class="spacer"></span>
</div>
<style>
/* … all the styles from the previous example … */
.container {
overflow: scroll;
}
.spacer {
display: block;
height: 500px;
}
</style>
และตอนนี้ก็เลื่อนกล่อง กล่องสีแดงจะเลื่อนลง
ขั้นตอนที่ 3: กำหนดขนาด
เรามีองค์ประกอบที่เลื่อนลงเมื่อหน้าเว็บเลื่อนลง นั่นแหละคือวิธี ที่ยากจริงๆ ตอนนี้เราต้องจัดสไตล์ให้ดูเหมือนแถบเลื่อนและทำให้อินเทอร์แอกทีฟมากขึ้น
โดยปกติแล้วแถบเลื่อนจะประกอบด้วย "แถบเลื่อน" และ "ร่อง" แต่ร่องอาจไม่แสดงเสมอไป ความสูงของนิ้วโป้งจะเป็นสัดส่วนโดยตรงกับปริมาณเนื้อหาที่มองเห็นได้
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
คือความสูงขององค์ประกอบที่เลื่อนได้ ส่วน
scroller.scrollHeight
คือความสูงทั้งหมดของเนื้อหาที่เลื่อนได้
scrollerHeight/scroller.scrollHeight
คือเศษส่วนของเนื้อหาที่มองเห็นได้ อัตราส่วนของพื้นที่แนวตั้งที่ครอบนิ้วหัวแม่มือควรเท่ากับอัตราส่วนของเนื้อหาที่มองเห็น
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
ขนาดของนิ้วโป้งดูดี แต่เคลื่อนไหวเร็วเกินไป นี่เป็นวิธีที่เราจะนำเทคนิคจากแถบเลื่อนแบบพารัลแลกซ์มาใช้ หากเราย้ายองค์ประกอบไปด้านหลังมากขึ้น องค์ประกอบจะเคลื่อนไหวช้าลงขณะเลื่อน เราแก้ไขขนาดได้โดยการปรับให้ใหญ่ขึ้น แต่เราควรจะถอยกลับมา ได้เท่าไหร่ล่ะ มาคำนวณกัน นี่เป็นครั้งสุดท้ายที่ผม สัญญานะว่า
ข้อมูลสำคัญที่สำคัญคือ เราต้องการให้ขอบด้านล่างของนิ้วโป้งอยู่ในแนวเดียวกับขอบด้านล่างขององค์ประกอบที่เลื่อนได้เมื่อเลื่อนลงจนสุด กล่าวคือ หากเลื่อนไป scroller.scrollHeight - scroller.height
พิกเซล เราต้องการให้นิ้วหัวแม่มือเลื่อนไป scroller.height - thumb.height
สําหรับพิกเซลของแถบเลื่อนแต่ละพิกเซล เราต้องการให้นิ้วหัวแม่มือเลื่อนไปเพียงเศษเสี้ยวของพิกเซล ดังนี้
นั่นคือตัวคูณมาตราส่วนของเรา ตอนนี้เราต้องแปลงปัจจัยการขยายเป็นการเปลี่ยนตำแหน่งตามแนวแกน z ซึ่งเราได้ทําไปแล้วในบทความการเลื่อนแบบพารัลแลกซ์ ตามที่ระบุไว้ใน
ส่วนที่เกี่ยวข้องในข้อกำหนด:
ค่าตัวคูณมาตราส่วนเท่ากับ p/(p − z) เราสามารถแก้สมการนี้สำหรับ z เพื่อหาว่าเราต้องแปลนิ้วโป้งให้อยู่ในแกน Z เป็นจำนวนเท่าใด แต่โปรดทราบว่าเนื่องจากความซับซ้อนของพิกัด w เราจึงต้องแปล -2px
เพิ่มเติมตาม z นอกจากนี้ โปรดทราบว่าระบบจะใช้การเปลี่ยนรูปแบบขององค์ประกอบจากขวาไปซ้าย ซึ่งหมายความว่าการแปลทั้งหมดก่อนเมตริกพิเศษจะไม่กลับหัว แต่การแปลทั้งหมดหลังเมตริกพิเศษจะกลับหัว มาเขียนโค้ดกัน
<script>
// ... code from above...
const factor =
(scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
thumb.style.transform = `
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
scale(${1/factor})
translateZ(${1 - 1/factor}px)
translateZ(-2px)
`;
</script>
เรามี แถบเลื่อน! และเป็นเพียงองค์ประกอบ DOM ที่เราสามารถจัดรูปแบบได้ตามใจชอบ สิ่งที่ต้องทำเพื่อรองรับการช่วยเหลือพิเศษอย่างหนึ่งคือทำให้แถบเลื่อนตอบสนองต่อการคลิกและลาก เนื่องจากผู้ใช้จํานวนมากคุ้นเคยกับการโต้ตอบกับแถบเลื่อนด้วยวิธีดังกล่าว เราจะไม่อธิบายรายละเอียดในส่วนนั้นเพื่อไม่ให้บล็อกโพสต์นี้ยาวเกินไป ลองดูที่โค้ดไลบรารีเพื่อดูรายละเอียดหากต้องการดูวิธีการทำงาน
แล้ว iOS ล่ะ
เพื่อนเก่าอย่าง iOS Safari เช่นเดียวกับการเลื่อนพารัลแลกซ์
เราพบปัญหาที่นี่ เนื่องจากเรากำลังเลื่อนองค์ประกอบ จึงต้องระบุ -webkit-overflow-scrolling: touch
แต่การระบุดังกล่าวจะทำให้ภาพ 3 มิติแบนราบและเอฟเฟกต์การเลื่อนทั้งหมดหยุดทำงาน เราได้แก้ปัญหานี้ในแถบเลื่อนภาพพารัลแลกซ์โดยการตรวจหา iOS Safari และใช้ position: sticky
เป็นวิธีแก้ปัญหาชั่วคราว และเราจะทำแบบเดียวกันกับที่นี่ ลองดูบทความพารัลแลกซ์เพื่อรีเฟรชความทรงจำ
แล้วแถบเลื่อนของเบราว์เซอร์ล่ะ
ในบางระบบ เราจะต้องจัดการกับแถบเลื่อนแบบเนทีฟถาวร
ที่ผ่านมา แถบเลื่อนจะซ่อนไม่ได้ (ยกเว้นตัวเลือกเสมือนที่ไม่เป็นมาตรฐาน)
ดังนั้นในการซ่อนข้อมูลดังกล่าว เราจึงต้องใช้วิธีแฮ็ก (ไม่ต้องใช้คณิตศาสตร์) เราตัดสิ้นสุดองค์ประกอบที่เลื่อนในคอนเทนเนอร์ด้วย overflow-x: hidden
และทำให้องค์ประกอบที่เลื่อนกว้างกว่าคอนเทนเนอร์ ตอนนี้แถบเลื่อนของเบราว์เซอร์จะมองไม่เห็นแล้ว
Fin
เมื่อนำทุกอย่างมารวมกัน ตอนนี้เราสามารถสร้างแถบเลื่อนที่ปรับแต่งเองซึ่งเฟรมจะพอดีกันทุกเฟรมได้ เช่น แถบเลื่อนในเดโมของ Nyan Cat
หากไม่เห็น Nyan Cat แสดงว่าคุณพบข้อบกพร่องที่เราพบและรายงานแล้วขณะสร้างเดโมนี้ (คลิกนิ้วโป้งเพื่อทำให้ Nyan Cat ปรากฏขึ้น) Chrome เก่งมากในการหลีกเลี่ยงการทำงานที่ไม่จำเป็น เช่น การวาดภาพหรือการสร้างภาพเคลื่อนไหวของสิ่งที่อยู่นอกหน้าจอ ข่าวร้ายคือการแสดงผลแบบ Matrix ของเราทําให้ Chrome คิดว่า GIF ของ Nyan Cat อยู่นอกหน้าจอ หวังว่าปัญหานี้จะได้รับการแก้ไขเร็วๆ นี้
เท่านี้ก็เรียบร้อย งานนี้ยากมาก เราชื่นชมที่คุณอ่านทุกอย่าง นี่เป็นวิธีที่ค่อนข้างซับซ้อนในการทำให้ใช้งานได้ และอาจไม่ค่อยคุ้มค่ากับเวลาและความพยายาม ยกเว้นในกรณีที่แถบเลื่อนที่ปรับแต่งเองเป็นส่วนสำคัญของประสบการณ์การใช้งาน แต่คุณก็ควรทราบว่าการดำเนินการดังกล่าวเป็นไปได้ การที่การทําแถบเลื่อนที่กําหนดเองนั้นทําได้ยากมากแสดงให้เห็นว่ายังมีงานที่ต้องทําในฝั่ง CSS แต่ไม่ต้องกังวลไป ในอนาคต AnimationWorklet ของ Houdini จะช่วยให้คุณสร้างเอฟเฟกต์ที่เชื่อมโยงกับการเลื่อนแบบเฟรมต่อเฟรมได้อย่างง่ายดาย