แถบเลื่อนที่กำหนดเองนั้นหายากมาก และส่วนใหญ่เกิดจากข้อเท็จจริงที่ว่าแถบเลื่อนเป็นหนึ่งในบิตที่เหลืออยู่บนเว็บซึ่งปรับแต่งไม่ได้เลย (ฉันกำลังมองคุณอยู่ เครื่องมือเลือกวันที่) คุณจะใช้ JavaScript เพื่อสร้าง JavaScript ของตัวเองได้ แต่ก็จะมีราคาแพง มีความแม่นยำต่ำ และอาจทำให้รู้สึกช้าได้ ในบทความนี้ เราจะใช้ประโยชน์จากเมทริกซ์ CSS แปลกใหม่เพื่อสร้างแถบเลื่อนแบบกำหนดเองที่ไม่ต้องใช้ JavaScript ขณะเลื่อน ขอเพียงโค้ดการตั้งค่าบางอย่างเท่านั้น
สรุปคร่าวๆ
ถ้าไม่สนใจอะไรเล็กๆ ก็พอ คุณแค่อยากดู การสาธิตแมว Nyan และเรียกดูคลังข้อมูล คุณดูโค้ดของการสาธิตได้ในที่เก็บของ GitHub
LAM;WRA (ยาวและอยู่ในเชิงคณิตศาสตร์ จะอ่านต่อไป)
เมื่อนานมาแล้ว เราสร้างเครื่องมือเลื่อนพารัลแลกซ์ (คุณอ่านบทความนั้นแล้วหรือยัง ซึ่งเป็นสิ่งที่ดีจริงๆ ที่คุ้มค่ากับการเสียเวลาของคุณ) การดันองค์ประกอบกลับโดยใช้การแปลงแบบ 3 มิติของ CSS ทำให้องค์ประกอบเคลื่อนที่ช้ากว่าความเร็วในการเลื่อนจริง
สรุป
มาเริ่มกันด้วยการสรุปวิธีการทำงานของแถบเลื่อนพารัลแลกซ์
ดังที่แสดงในภาพเคลื่อนไหว เราสร้างเอฟเฟกต์พารัลแลกซ์ได้ด้วยการดันองค์ประกอบ “ย้อนกลับ” ไปในพื้นที่ 3 มิติตามแกน Z การเลื่อนเอกสารเป็นการแปลอย่างมีประสิทธิภาพ ตามแกน Y ดังนั้นถ้าเราเลื่อนลง 100 พิกเซล องค์ประกอบทุกรายการจะได้รับการแปลขึ้น 100 พิกเซล ซึ่งใช้ได้กับองค์ประกอบทั้งหมด แม้แต่ตัวที่อยู่ "ด้านหลัง" แต่เนื่องจากอยู่ไกลจากกล้อง การเคลื่อนไหวบนหน้าจอที่สังเกตได้จะมีขนาดเล็กกว่า 100 พิกเซล ทำให้ได้เอฟเฟ็กต์พารัลแลกซ์ตามที่ต้องการ
แน่นอนว่าการย้ายองค์ประกอบกลับไปในอวกาศจะทำให้มีขนาดเล็กลง ซึ่งเราแก้ไขด้วยการปรับขนาดองค์ประกอบสำรอง เราเข้าใจหลักการคำนวณที่ถูกต้องแล้ว ตอนที่สร้างตัวเลื่อนพารัลแลกซ์ ฉันจึงจะไม่บอกรายละเอียดทั้งหมดซ้ำ
ขั้นตอนที่ 0: คุณอยากทำอะไรบ้าง
แถบเลื่อน และเป็นสิ่งที่เรากำลังจะสร้าง แต่คุณเคยนึกถึง สิ่งที่พวกเขาทำบ้างไหม ว่าแต่คิดว่าไม่ตอบเลย แถบเลื่อนคือตัวบ่งชี้ปริมาณเนื้อหาที่มีอยู่ซึ่งมองเห็นได้ในปัจจุบัน และความคืบหน้าของคุณในฐานะผู้อ่าน หากเลื่อนลงไป แถบเลื่อนก็จะบ่งบอกว่าคุณกำลังเข้าใกล้จุดสิ้นสุดแล้วเช่นกัน หากเนื้อหาทั้งหมดพอดีกับวิวพอร์ต โดยปกติแถบเลื่อนจะซ่อนอยู่ หากเนื้อหามีความสูงเป็น 2 เท่าของวิวพอร์ต แถบเลื่อนจะมีขนาดเท่ากับ 1⁄2 ของความสูงของวิวพอร์ต เนื้อหาซึ่งมีมูลค่าเป็น 3 เท่าของความสูงของวิวพอร์ตจะปรับขนาดแถบเลื่อนเป็น 1⁄3 ของวิวพอร์ต เป็นต้น คุณยังคลิกและลากแถบเลื่อนเพื่อเลื่อนไปยังส่วนต่างๆ ในเว็บไซต์ได้เร็วขึ้นแทนการเลื่อนได้อีกด้วย นี่เป็นพฤติกรรมที่น่าประหลาดใจสำหรับองค์ประกอบที่ไม่ชัดเจนแบบนั้น เข้าร่วมการต่อสู้ทีละรายการ
ขั้นตอนที่ 1: ใส่อุปกรณ์กลับด้าน
โอเค เราทำให้องค์ประกอบเคลื่อนไหวได้ช้ากว่าความเร็วในการเลื่อนด้วยการแปลง CSS 3 มิติ ตามที่ระบุไว้ในบทความการเลื่อนพารัลแลกซ์ เราจะกลับทิศทางได้ไหม เราพอจะทำเช่นนั้นได้ และนี่คือแนวทางในการสร้างแถบเลื่อนที่กำหนดเอง สมบูรณ์แบบ เพื่อให้เข้าใจวิธีการทำงาน เราจำเป็นต้องพูดถึง ข้อมูลพื้นฐานเกี่ยวกับ CSS 3 มิติบางส่วนก่อน
ในการมองภาพในเชิงคณิตศาสตร์ในลักษณะใดก็ตาม ก็มักจะต้องใช้พิกัดที่เป็นเอกภาพ ผมจะไม่อธิบายรายละเอียดว่ากฎเหล่านี้คืออะไรและทำไมถึงทำงานอย่างนี้ แต่คุณอาจมองได้ว่าเป็นพิกัด 3 มิติที่มีพิกัดเพิ่มเติมตัวที่ 4 ที่เรียกว่า w พิกัดนี้ควรเป็น 1 ยกเว้นในกรณีที่คุณต้องการให้มุมมองที่บิดเบี้ยว เราไม่ต้องกังวลเกี่ยวกับรายละเอียดของ w เนื่องจากเราจะไม่ใช้ค่าอื่นใดนอกจาก 1 ดังนั้น จากนี้ไปจุดทั้งหมดจึงมาจากเวกเตอร์ 4 มิติ [x, y, z, w=1] และผลก็คือเมทริกซ์ก็ต้องเป็น 4x4 เช่นกัน
โอกาสหนึ่งที่คุณจะเห็นว่า CSS ใช้พิกัดเดียวกันภายในระบบคือเมื่อคุณกําหนดเมทริกซ์ 4x4 ของคุณเองในพร็อพเพอร์ตี้ Transform โดยใช้ฟังก์ชัน matrix3d()
matrix3d
รับอาร์กิวเมนต์ 16 อาร์กิวเมนต์ (เนื่องจากเมทริกซ์มีขนาด 4x4) โดยระบุ 1 คอลัมน์ต่อจากอีกคอลัมน์ เราจึงสามารถใช้ฟังก์ชันนี้เพื่อระบุการหมุน การแปล และอื่นๆ ด้วยตนเอง แต่สิ่งที่ทำให้เราทำได้ก็คือการแก้ไขพิกัด w นั้น
ก่อนที่เราจะใช้ประโยชน์จาก matrix3d()
เราจำเป็นต้องมีบริบทแบบ 3 มิติ เพราะหากไม่มีบริบท 3 มิติ ก็จะไม่มีการบิดเบี้ยวของมุมมองและไม่จำเป็นต้องมีพิกัดแบบเดียวกัน ในการสร้างบริบท 3 มิติ เราต้องมีคอนเทนเนอร์ที่มี perspective
และองค์ประกอบบางอย่างด้านในที่เราจะเปลี่ยนรูปแบบในพื้นที่ 3 มิติที่สร้างขึ้นใหม่ได้ เช่น
ตัวอย่าง
![โค้ด CSS ชิ้นหนึ่งที่บิดเบี้ยว div โดยใช้แอตทริบิวต์มุมมองของ CSS](https://developer.chrome.com/static/docs/css-ui/custom-scrollbar/image/a-piece-css-code-distor-1ed892aadfd7c.png?authuser=5&hl=th)
องค์ประกอบภายในคอนเทนเนอร์มุมมองจะได้รับการประมวลผลโดยเครื่องมือ CSS ดังนี้
- เปลี่ยนแต่ละมุม (จุดยอดมุม) ขององค์ประกอบให้เป็นพิกัดเดียวกัน
[x,y,z,w]
ซึ่งสัมพันธ์กับที่เก็บมุมมอง - ใช้การแปลงทั้งหมดขององค์ประกอบเป็นเมทริกซ์จากขวาไปซ้าย
- หากองค์ประกอบมุมมองสามารถเลื่อนได้ ให้ใช้เมทริกซ์การเลื่อน
- ใช้เมทริกซ์มุมมอง
เมทริกซ์การเลื่อนคือการแปลตามแกน y หากเราเลื่อนลง 400 พิกเซล องค์ประกอบทั้งหมดจะต้องเลื่อนขึ้น 400 พิกเซล เมทริกซ์มุมมองคือเมทริกซ์ที่ "ดึง" ชี้ไปใกล้จุดหายไปมากกว่าในพื้นที่ 3 มิติ ซึ่งบรรลุผลทั้ง 2 อย่างคือ ทำให้ข้อความดูเล็กลงเมื่ออยู่ไกลออกไป และยังทำให้ "เคลื่อนไหวช้าลง" ขณะแปล ดังนั้นหากมีการถอยหลังกลับไป 400 พิกเซลจะทำให้องค์ประกอบเลื่อนไปเพียง 300 พิกเซลบนหน้าจอ
หากต้องการทราบรายละเอียดทั้งหมด คุณควรอ่านspecเกี่ยวกับโมเดลการแสดงผลการเปลี่ยนรูปแบบของ CSS แต่สำหรับบทความนี้ เราได้ลดความซับซ้อนของอัลกอริทึมข้างต้น
กล่องของเราอยู่ในคอนเทนเนอร์มุมมองที่มีค่า p สำหรับแอตทริบิวต์ perspective
และสมมติว่าคอนเทนเนอร์เลื่อนได้และเลื่อนลงได้ n พิกเซล
เมทริกซ์แรกคือเมทริกซ์มุมมอง เมทริกซ์ที่สองคือเมทริกซ์การเลื่อน กล่าวโดยสรุป หน้าที่ของเมทริกซ์การเลื่อนคือการทำให้องค์ประกอบย้ายขึ้นเมื่อเราเลื่อนลง จึงทำให้เกิดเครื่องหมายลบ
แต่สำหรับแถบเลื่อนของเรา เราต้องการสิ่งตรงข้าม กล่าวคือ เราต้องการให้องค์ประกอบย้ายลงเมื่อเลื่อนลง เคล็ดลับที่เราใช้คือ
การกลับพิกัด 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]
ฉันได้ระบุขั้นตอนระดับกลางเพื่อแสดงผลของเมทริกซ์การแปลงองค์ประกอบ ถ้าคุณไม่ชอบคณิตศาสตร์เมทริกซ์ ก็ไม่เป็นไร ช่วงเวลาของยูเรกาคือ ในบรรทัดสุดท้ายเราจะเพิ่มค่าออฟเซ็ตการเลื่อน n ลงในพิกัด y ของเราแทนที่จะลบออก ระบบจะแปลองค์ประกอบลง หากเราเลื่อนลง
อย่างไรก็ตาม หากเราแค่ใส่เมทริกซ์นี้ในตัวอย่าง องค์ประกอบจะยังไม่แสดง เนื่องจากข้อกำหนดของ CSS กำหนดให้จุดยอดมุมที่มี w < 0 บล็อกองค์ประกอบไม่ให้แสดง และเนื่องจากพิกัด z ของเราตอนนี้คือ 0 และ p คือ 1 ส่วน w จึงเป็น -1
โชคดีที่เราเลือกค่า z ได้ จะได้ w=1 ได้ เราต้องตั้ง z = -2
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
เอาเถอะ กล่องของเรา กลับมาแล้ว!
ขั้นตอนที่ 2: เคลื่อนไหวร่างกาย
ตอนนี้กล่องของเราอยู่ในนั้นแล้ว และมีลักษณะแบบเดียวกับที่จะไม่มีการเปลี่ยนแปลงใดๆ ตอนนี้คอนเทนเนอร์มุมมองไม่สามารถเลื่อนได้ เราจึงมองไม่เห็น แต่เรารู้ว่าองค์ประกอบจะเลื่อนไปทิศทางอื่นเมื่อเลื่อน มาทำให้คอนเทนเนอร์เลื่อนได้กัน ตกลงไหม เราสามารถเพิ่ม องค์ประกอบ Spacer ที่กินพื้นที่ โดยทำดังนี้
<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
เป็นวิธีแก้ปัญหาเบื้องต้น และเราจะทำเช่นเดียวกันนี้ ลองดูบทความพารัลแลกซ์เพื่อรีเฟรชความทรงจำ
แล้วแถบเลื่อนของเบราว์เซอร์จะเป็นอย่างไร
ในบางระบบ เราจะต้องจัดการกับแถบเลื่อนแบบเนทีฟอย่างถาวร
ในอดีต แถบเลื่อนจะซ่อนไม่ได้ (ยกเว้นเมื่อใช้ Pseudo-selector ที่ไม่ใช่แบบมาตรฐาน)
เราจึงต้องรีบจัดการกับการแฮ็ก (ไม่มีคณิตศาสตร์) เพื่อซ่อนไว้ เรารวมองค์ประกอบการเลื่อนของเราไว้ในคอนเทนเนอร์ด้วย overflow-x: hidden
และทำให้องค์ประกอบแบบเลื่อนกว้างกว่าคอนเทนเนอร์ แถบเลื่อนเนทีฟของเบราว์เซอร์
ไม่ได้แสดงในขณะนี้แล้ว
เสร็จสิ้น
เมื่อนำทุกอย่างมารวมเข้าด้วยกัน ตอนนี้เราก็สามารถสร้างแถบเลื่อนที่กำหนดเองที่สมบูรณ์แบบ เช่นในการสาธิตแมว Nyan
หากคุณไม่เห็นแมว Nyan แสดงว่าคุณมีข้อบกพร่องที่เราพบและได้แจ้งไว้ขณะสร้างการสาธิตนี้ (คลิกนิ้วโป้งเพื่อให้แมว Nyan ปรากฏขึ้น) Chrome เก่งมากในการหลีกเลี่ยงงานที่ไม่จำเป็น เช่น การวาดภาพหรือการสร้างภาพเคลื่อนไหวให้กับสิ่งที่อยู่บนหน้าจอ ข่าวร้ายคือ การอำพรางตัวในเมทริกซ์ ทำให้ Chrome คิดว่า GIF แมว Nyan หลุดจากจอแล้ว เราหวังว่าปัญหานี้จะได้รับการแก้ไขในเร็วๆ นี้
เท่านี้ก็เรียบร้อย เป็นงานที่หนักมาก ขอขอบคุณ ที่อ่านเนื้อหาทั้งหมด สิ่งนี้เป็นกลอุบายที่แท้จริงบางอย่างในการได้ผลและน่าจะไม่ค่อยคุ้มค่ากับความพยายาม ยกเว้นในกรณีที่แถบเลื่อนที่กำหนดเองเป็นส่วนสำคัญของประสบการณ์การใช้งาน แต่ก็ดีที่ได้รู้ว่าเป็นไปได้ ไม่งั้น ความจริงที่ว่าการสร้างแถบเลื่อนที่กำหนดเองนั้นทำได้ยากแสดงให้เห็นว่า CSS มีงานต้องทำอีกมาก แต่ไม่ต้องกังวลไป ในอนาคต AnimationWorklet ของ Houdini จะทำให้ เอฟเฟกต์แบบเลื่อนในเฟรมสมบูรณ์แบบง่ายขึ้นมาก กว่าเดิม