Scrollbar kustom sangat langka dan sebagian besar disebabkan oleh bilah gulir adalah salah satu bit yang tersisa di web yang cukup tidak bisa ditiru (saya sedang melihat Anda, pemilih tanggal). Anda dapat menggunakan JavaScript untuk membuatnya sendiri, tetapi itu mahal, rendah {i>fidelity <i}dan bisa terasa lambat. Dalam artikel ini, kami akan memanfaatkan beberapa matriks CSS non-konvensional untuk membuat scroller khusus yang tidak memerlukan JavaScript saat men-scroll, hanya beberapa kode penyiapan.
TL;DR
Kamu nggak peduli dengan hal-hal kecil? Anda hanya ingin melihat Demo kucing Nyan dan mendapatkan perpustakaan? Anda dapat menemukan kode demo di Repositori GitHub.
LAM;WRA (Panjang dan matematis; tetap akan dibaca)
Beberapa waktu yang lalu, kami membuat {i>scroller<i} paralaks (Apakah Anda membaca artikel tersebut? Hasilnya sangat baik dan sepadan dengan waktu Anda.). Dengan mendorong elemen kembali menggunakan CSS 3D mengubah, elemen yang bergerak lebih lambat daripada kecepatan scroll yang sebenarnya.
Rekap
Mari kita mulai dengan rangkuman cara kerja scroller paralaks.
Seperti yang ditunjukkan di animasi, kita mendapatkan efek paralaks dengan mendorong elemen “mundur” dalam ruang 3D, di sepanjang sumbu Z. Menggulir dokumen secara efektif di sepanjang sumbu Y. Jadi, jika kita men-scroll ke bawah, misalnya 100px, setiap akan diterjemahkan ke atas sebesar 100px. Ini berlaku untuk semua elemen, bahkan yang "tertinggal". Namun karena jaraknya lebih jauh dari kamera, gerakan yang teramati di layar akan kurang dari 100 piksel, sehingga menghasilkan efek paralaks yang diinginkan.
Tentu saja, memindahkan elemen kembali ke luar ruangan juga akan membuatnya tampak lebih kecil, yang kita perbaiki dengan memperbesar kembali elemen tersebut. Kami menemukan perhitungan yang tepat saat kita membuat scroller paralaks, jadi saya tidak akan mengulangi semua detailnya.
Langkah 0: Apa yang ingin kita lakukan?
Scrollbar. Itulah yang akan kita bangun. Tetapi apakah Anda pernah berpikir tentang apa yang mereka lakukan? Saya tentu tidak tahu. Bilah gulir merupakan indikator dari berapa banyak konten tersedia yang saat ini terlihat dan berapa banyak progresnya Anda sebagai pembaca. Jika Anda menggulir ke bawah, juga {i>scrollbar<i} untuk menunjukkan bahwa Anda membuat kemajuan menjelang akhir proyek. Jika semua konten sesuai ke area pandang, scrollbar biasanya tersembunyi. Jika konten memiliki 2x tinggi area pandang, scrollbar mengisi 1⁄2 dari tinggi area pandang. Konten bernilai 3x tinggi area pandang menskalakan scrollbar ke 1⁄3 area pandang, dsb. Anda akan melihat polanya. Alih-alih menggulir, Anda juga dapat mengeklik dan menyeret {i>scrollbar<i} untuk situs lebih cepat. Itu adalah jumlah perilaku yang mengejutkan untuk orang yang tidak seperti itu. Mari kita bertarung satu per satu.
Langkah 1: Membalikkannya
Baiklah, kita dapat membuat elemen bergerak lebih lambat dari kecepatan scroll dengan CSS 3D seperti yang diuraikan dalam artikel {i>scrolling<i} paralaks. Dapatkah kita juga membalikkan arahnya? Ternyata kami bisa dan itulah jalan kami untuk membangun {i>frame-sempurna<i}, scrollbar khusus. Untuk memahami cara kerjanya, kita perlu membahas beberapa dasar 3D CSS terlebih dahulu.
Untuk mendapatkan proyeksi perspektif apa pun dalam arti matematis, Anda akan kemungkinan besar akan menggunakan koordinat homogen. Saya tidak akan membahas apa dan mengapa mereka bekerja secara rinci, tetapi bisa Anda pikirkan seperti koordinat 3D dengan koordinat tambahan keempat yang disebut w. Ini koordinat harus 1 kecuali jika Anda ingin mendapatkan distorsi perspektif. Rab tidak perlu khawatir tentang detail w karena kita tidak akan menggunakan nilai selain 1. Oleh karena itu, semua titik berasal dari vektor 4 dimensi [x, y, z, w=1] dan oleh karena itu matriks membutuhkan menjadi 4x4 juga.
Suatu kesempatan di mana Anda dapat melihat bahwa CSS menggunakan koordinat homogen di bawah
adalah ketika Anda menentukan matriks 4 x 4 Anda sendiri dalam properti transformasi menggunakan
Fungsi matrix3d()
. matrix3d
memerlukan 16 argumen (karena matriksnya adalah
4x4), yang menentukan satu kolom demi kolom. Kita bisa menggunakan
fungsi ini untuk
menentukan rotasi, terjemahan, dll. secara manual. Tapi hal itu juga memungkinkan kita
mengotak-atik koordinat w itu.
Sebelum dapat menggunakan matrix3d()
, kita memerlukan konteks 3D – karena tanpa
Konteks 3D tidak akan terdistorsi
perspektif dan tidak diperlukan
koordinat homogen. Untuk membuat konteks 3D, kita membutuhkan penampung dengan
perspective
dan beberapa elemen di dalamnya yang dapat kita ubah dalam
menciptakan ruang 3D. Sebagai
contoh:
![Potongan kode CSS yang mendistorsi div menggunakan elemen
perspektif.](https://developer.chrome.google.cn/static/docs/css-ui/custom-scrollbar/image/a-piece-css-code-distor-1ed892aadfd7c.png?authuser=7&hl=id)
Elemen di dalam penampung perspektif diproses oleh mesin CSS sebagai berikut:
- Mengubah setiap sudut (vertex) elemen menjadi koordinat homogen
[x,y,z,w]
, relatif terhadap penampung perspektif. - Terapkan semua transformasi elemen sebagai matriks dari kanan ke kiri.
- Jika elemen perspektif dapat di-scroll, terapkan matriks scroll.
- Terapkan matriks perspektif.
Matriks scroll adalah terjemahan di sepanjang sumbu y. Jika kita scroll ke bawah dengan 400 px, semua elemen harus dipindahkan ke atas sebesar 400 px. Matriks perspektif adalah matriks yang “menarik” menunjuk lebih dekat ke titik hilang yang semakin jauh ke belakang dalam 3D jarak yang tepat. Hal ini akan menghasilkan kedua hal, yaitu pembuatan hal-hal yang tampak lebih kecil mundur dan juga membuat mereka “bergerak lebih lambat” saat diterjemahkan. Jadi, jika sebuah elemen didorong ke belakang, terjemahan 400 px akan menyebabkan elemen bergerak hanya sejauh 300 px di layar.
Jika Anda ingin mengetahui semua detailnya, Anda harus membaca spesifikasi di elemen mengubah model rendering, tetapi untuk artikel ini, saya telah menyederhanakan algoritma di atas.
Kotak kita ada di dalam penampung perspektif dengan nilai p untuk perspective
, dan asumsikan container-nya dapat di-scroll dan di-scroll ke bawah oleh
n piksel.
Matriks pertama adalah matriks perspektif, matriks kedua adalah {i>scroll<i} yang dihasilkan. Sebagai rangkuman: Tugas matriks scroll adalah membuat elemen bergerak ke atas saat kita men-scroll ke bawah, sehingga menjadi tanda negatif.
Namun, untuk scrollbar, kita menginginkan yang kebalikannya – kita ingin elemen
turunkan ke bawah saat men-scroll ke bawah. Di sinilah kita dapat menggunakan trik:
Membalik koordinat w dari sudut-sudut kotak kita. Jika koordinat w adalah
-1, semua terjemahan akan berlaku ke arah yang berlawanan. Jadi bagaimana kita melakukan
itu? Mesin CSS menangani konversi
sudut dari kotak kita menjadi
koordinat homogen, dan menetapkan w ke 1. Inilah saatnya matrix3d()
kembali bersinar!
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
Matriks ini tidak akan melakukan apa pun selain menegasikan w. Jadi, ketika mesin CSS memiliki
mengubah setiap sudut menjadi vektor bentuk [x,y,z,1]
, matriks akan
mengonversinya menjadi [x,y,z,-1]
.
Saya mencantumkan langkah perantara untuk menunjukkan efek transformasi elemen kita yang dihasilkan. Jika Anda tidak terbiasa dengan matematika matriks, tidak apa-apa. Eureka di baris terakhir kita menambahkan offset scroll n ke y mengkoordinasikan alih-alih menguranginya. Elemen ini akan diterjemahkan ke bawah jika kita men-scroll ke bawah.
Namun, jika kita hanya memasukkan matriks ini ke dalam contoh, elemen tidak akan ditampilkan. Hal ini karena spesifikasi CSS mengharuskan verteks dengan w < 0 memblokir elemen agar tidak dirender. Dan karena z kita koordinat saat ini adalah 0, dan p adalah 1, w akan menjadi -1.
Untungnya, kita bisa memilih nilai z. Untuk memastikan kita mendapatkan nilai w=1, kita perlu untuk mengatur z = -2.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
Lihatlah, box hadir kembali!
Langkah 2: Bergeraklah
Sekarang kotak kita ada di sana dan terlihat sama seperti sebelumnya tanpa transform. Saat ini, container perspektif tidak dapat di-scroll, sehingga kita tidak dapat melihatnya, tetapi kita tahu bahwa elemen akan pergi ke arah lain saat di-scroll. Mari kita scroll container, bukan? Kita cukup menambahkan elemen pengatur jarak yang membutuhkan ruang:
<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>
Dan sekarang scroll kotaknya! Kotak merah bergerak ke bawah.
Langkah 3: Beri ukuran
Kita memiliki elemen yang bergerak ke bawah ketika halaman di-scroll ke bawah. Itu yang sulit sedikit keluar. Sekarang kita perlu menata gayanya agar terlihat seperti {i>scrollbar<i} dan membuatnya sedikit lebih interaktif.
Scrollbar biasanya terdiri dari "jempol" dan "trek", sedangkan trek tidak selalu terlihat. Tinggi jempol berbanding lurus dengan seberapa besar konten dapat dilihat.
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
adalah tinggi elemen yang dapat di-scroll, sedangkan
scroller.scrollHeight
adalah tinggi total konten yang dapat di-scroll.
scrollerHeight/scroller.scrollHeight
adalah bagian dari konten yang
terlihat. Rasio ruang vertikal yang ditutup ibu jari harus sama dengan
rasio konten yang terlihat:
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
Ukuran ibu jari adalah tampak bagus, tapi bergerak terlalu cepat. Di sinilah kita bisa mengambil teknik dari scroller paralaks. Jika kita memindahkan elemen lebih jauh ke belakang, elemen akan bergerak lebih lambat saat untuk men-scroll. Kita dapat memperbaiki ukuran dengan meningkatkan skalanya. Tapi seberapa banyak kita harus mendorong kembali dengan tepat? Ayo mulai matematika! Ini adalah terakhir kalinya, saya yang menjanjikan.
Informasi penting adalah bahwa kita
ingin tepi bawah ibu jari untuk
sejajar dengan tepi bawah elemen yang dapat di-scroll saat di-scroll sepenuhnya
ke bawah. Dengan kata lain: Jika kita men-scroll
scroller.scrollHeight - scroller.height
piksel, kita ingin thumbnail
diterjemahkan oleh scroller.height - thumb.height
. Untuk setiap {i>pixel<i} scroller, kita
ingin ibu jari kita
memindahkan sepersekian piksel:
Itulah faktor penskalaan kami. Sekarang kita perlu mengonversi faktor penskalaan menjadi
di sepanjang sumbu z, yang telah kita lakukan dalam scroll paralaks
artikel. Menurut
bagian yang relevan dalam spesifikasi:
Faktor penskalaan sama dengan p/(p - z). Kita dapat menyelesaikan
persamaan ini untuk z
mencari tahu seberapa banyak kita perlu menerjemahkan
jempol kita sepanjang sumbu z. Namun pertahankan
diingat bahwa karena kesalahan koordinat {i>w<i},
kita perlu menerjemahkan sebuah
-2px
tambahan di sepanjang z. Perhatikan juga bahwa transformasi elemen diterapkan
kanan ke kiri, yang berarti bahwa semua terjemahan
sebelum matriks khusus kita tidak akan
dibalik, tapi semua terjemahan setelah matriks khusus kita akan dibalik! Mari
dapat menyusunnya!
<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>
Tersedia scrollbar! Dan itu hanya elemen DOM yang bisa kita tata gayanya sesuka kita. Satu hal yang penting untuk dilakukan dalam hal aksesibilitas adalah membuat ibu jari merespons klik-dan-seret, karena banyak pengguna yang terbiasa berinteraksi dengan scrollbar seperti itu. Supaya postingan blog ini tidak panjang, saya tidak akan menjelaskan detail untuk bagian tersebut. Lihat kode library untuk mengetahui detailnya jika ingin melihat cara melakukannya.
Bagaimana dengan iOS?
Ah, teman lama saya iOS Safari. Seperti halnya scroll paralaks, kita menemukan
di sini. Karena kita men-scroll pada elemen, kita perlu menentukan
-webkit-overflow-scrolling: touch
, tetapi itu akan menyebabkan perataan 3D dan seluruh
efek scroll berhenti berfungsi. Kita menyelesaikan masalah ini di scroller paralaks
dengan mendeteksi iOS Safari dan mengandalkan position: sticky
sebagai solusi, serta
kami akan melakukan
hal yang sama di sini. Lihat
artikel paralaks
untuk menyegarkan ingatan Anda.
Bagaimana dengan scrollbar browser?
Di beberapa sistem, kita harus menangani scrollbar native yang permanen.
Secara historis, scrollbar tidak dapat disembunyikan (kecuali dengan
pemilih pseudo non-standar).
Jadi untuk menyembunyikannya, kita harus melakukan beberapa peretas (bebas matematika). Kita menggabungkan
scroll dalam container dengan overflow-x: hidden
, lalu buat elemen
elemen scroll yang lebih lebar dari container. Scrollbar native browser
sekarang tidak terlihat.
Fin
Dengan menggabungkan semuanya, sekarang kita dapat membuat frame kustom scrollbar – seperti yang ada di Demo kucing Nyan.
Jika Anda tidak dapat melihat kucing Nyan, Anda sedang bug yang kami temukan dan laporkan saat membuat demo ini (klik jempol untuk memunculkan kucing Nyan). Chrome sangat hebat dalam menghindari pekerjaan yang tidak perlu seperti melukis atau membuat animasi hal-hal yang berada di luar layar. Kabar buruknya adalah kekejaman matriks membuat Chrome berpikir bahwa gif kucing Nyan sebenarnya ada di balik layar. Semoga masalah ini bisa segera diperbaiki.
Seperti itu. Itu adalah pekerjaan yang merepotkan. Saya memuji Anda karena telah membaca seluruh sesuatu. Ini adalah beberapa trik nyata agar cara ini berhasil dan mungkin jarang sepadan dengan usahanya, kecuali jika scrollbar yang disesuaikan adalah bagian penting dari pengalaman ini. Tapi senang mengetahui bahwa hal itu mungkin terjadi, bukan? Kenyataan bahwa sulit untuk melakukan scrollbar kustom menunjukkan bahwa ada pekerjaan yang harus dilakukan di sisi CSS. Tapi jangan khawatir! Di masa mendatang, Houdini AnimationWorklet akan membuat efek scroll-link {i>frame-sempurna<i} seperti ini yang jauh lebih mudah.