Scrollbar kustom sangat jarang digunakan dan hal ini sebagian besar disebabkan oleh fakta bahwa scrollbar adalah salah satu bagian yang tersisa di web yang hampir tidak dapat ditata gayanya (saya melihat Anda, pemilih tanggal). Anda dapat menggunakan JavaScript untuk membuat sendiri, tetapi harganya mahal, fidelitasnya rendah, dan dapat terasa lambat. Dalam artikel ini, kita akan memanfaatkan beberapa matriks CSS yang tidak konvensional untuk membuat scroll kustom yang tidak memerlukan JavaScript saat men-scroll, hanya beberapa kode penyiapan.
TL;DR
Anda tidak peduli dengan hal-hal kecil? Anda hanya ingin melihat demo Nyanyan cat dan mendapatkan library? Anda dapat menemukan kode demo di repo GitHub kami.
LAM;WRA (Panjang dan matematis; akan tetap dibaca)
Beberapa waktu lalu, kita telah membuat scroll parallax (Sudahkah Anda membaca artikel tersebut? Sangat bagus, sangat sepadan dengan waktu Anda). Dengan mendorong elemen kembali menggunakan transformasi CSS 3D, elemen akan bergerak lebih lambat daripada kecepatan scroll sebenarnya.
Rekap
Mari kita mulai dengan recap cara kerja scroll parallax.
Seperti yang ditunjukkan dalam animasi, kita mendapatkan efek paralaks dengan mendorong elemen "mundur" dalam ruang 3D, di sepanjang sumbu Z. Men-scroll dokumen secara efektif merupakan terjemahan di sepanjang sumbu Y. Jadi, jika kita men-scroll ke bawah, misalnya 100 px, setiap elemen akan diterjemahkan ke atas sebesar 100 px. Hal ini berlaku untuk semua elemen, bahkan yang “lebih jauh”. Namun, karena elemen tersebut lebih jauh dari kamera, gerakannya di layar yang diamati akan kurang dari 100 piksel, sehingga menghasilkan efek paralaks yang diinginkan.
Tentu saja, memindahkan elemen kembali ke ruang juga akan membuatnya tampak lebih kecil, yang kita perbaiki dengan menskalakan elemen kembali. Kita telah mengetahui matematika yang tepat saat membuat parallax scroller, jadi saya tidak akan mengulangi semua detailnya.
Langkah 0: Apa yang ingin kita lakukan?
Scroll bar. Itulah yang akan kita buat. Namun, pernahkah Anda benar-benar memikirkan apa yang mereka lakukan? Tentu saja tidak. Scrollbar adalah indikator jumlah konten yang tersedia saat ini dan progres yang telah Anda capai sebagai pembaca. Jika Anda men-scroll ke bawah, scrollbar juga akan men-scroll ke bawah untuk menunjukkan bahwa Anda sedang mencapai akhir. Jika semua konten sesuai dengan area pandang, scrollbar biasanya disembunyikan. Jika konten memiliki tinggi 2x dari area pandang, scrollbar akan mengisi ½ tinggi area pandang. Konten yang bernilai 3x tinggi area pandang akan menskalakan scrollbar ke ⅓ area pandang, dll. Anda akan melihat polanya. Sebagai ganti scroll, Anda juga dapat mengklik dan menarik scrollbar untuk berpindah di situs dengan lebih cepat. Jumlah perilaku yang mengejutkan untuk elemen yang tidak mencolok seperti itu. Mari kita hadapi satu per satu.
Langkah 1: Mundur
Baik, kita dapat membuat elemen bergerak lebih lambat dari kecepatan scroll dengan transformasi CSS 3D seperti yang diuraikan dalam artikel scroll paralaks. Dapatkah kita juga membalikkan arahnya? Ternyata kita bisa, dan itulah cara kita untuk membuat scrollbar kustom yang sempurna. Untuk memahami cara kerjanya, kita perlu membahas beberapa dasar CSS 3D terlebih dahulu.
Untuk mendapatkan jenis proyeksi perspektif apa pun dalam arti matematika, Anda kemungkinan besar akan menggunakan koordinat homogen. Saya tidak akan menjelaskan detailnya dan mengapa hal ini berfungsi, tetapi Anda dapat menganggapnya seperti koordinat 3D dengan koordinat keempat tambahan yang disebut w. Koordinat ini harus 1 kecuali jika Anda ingin memiliki distorsi perspektif. Kita tidak perlu khawatir dengan detail w karena kita tidak akan menggunakan nilai selain 1. Oleh karena itu, semua titik mulai sekarang adalah vektor 4 dimensi [x, y, z, w=1] dan akibatnya matriks juga harus berukuran 4x4.
Salah satu kesempatan untuk melihat bahwa CSS menggunakan koordinat homogen di
bawah adalah saat Anda menentukan matriks 4x4 Anda sendiri dalam properti transformasi menggunakan
fungsi matrix3d()
. matrix3d
menggunakan 16 argumen (karena matriksnya
4x4), yang menentukan satu kolom setelah yang lain. Jadi, kita dapat menggunakan fungsi ini untuk
menentukan rotasi, terjemahan, dll. secara manual. Namun, fungsi ini juga memungkinkan kita
mengubah koordinat w tersebut.
Sebelum dapat menggunakan matrix3d()
, kita memerlukan konteks 3D – karena tanpa
konteks 3D, tidak akan ada distorsi perspektif dan tidak perlu
koordinat homogen. Untuk membuat konteks 3D, kita memerlukan penampung dengan
perspective
dan beberapa elemen di dalamnya yang dapat kita ubah di ruang 3D
yang baru dibuat. Misalnya:
Elemen di dalam penampung perspektif diproses oleh mesin CSS sebagai berikut:
- Ubah setiap sudut (vertikal) 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 men-scroll ke bawah sebesar 400 piksel, semua elemen harus dipindahkan ke atas sebesar 400 piksel. Matriks perspektif adalah matriks yang "menarik" titik lebih dekat ke titik hilang semakin jauh ke belakang dalam ruang 3D. Hal ini akan menghasilkan efek yang membuat objek tampak lebih kecil saat lebih jauh ke belakang dan juga membuat objek “bergerak lebih lambat” saat diterjemahkan. Jadi, jika elemen didorong kembali, terjemahan 400 piksel akan menyebabkan elemen hanya bergerak 300 piksel di layar.
Jika ingin mengetahui semua detailnya, Anda harus membaca spesifikasi tentang model rendering transformasi CSS, tetapi untuk artikel ini, saya menyederhanakan algoritma di atas.
Kotak kita berada di dalam penampung perspektif dengan nilai p untuk atribut
perspective
, dan mari kita asumsikan penampung dapat di-scroll dan di-scroll ke bawah dengan
n piksel.
Matriks pertama adalah matriks perspektif, matriks kedua adalah matriks scroll. Untuk merangkum: Tugas matriks scroll adalah membuat elemen bergerak ke atas saat kita men-scroll ke bawah, sehingga tanda negatif.
Namun, untuk scrollbar, kita menginginkan hal yang berlawanan – kita ingin elemen kita
bergerak ke bawah saat kita men-scroll ke bawah. Di sinilah kita dapat menggunakan trik:
Menginversi koordinat w dari sudut kotak. Jika koordinat w adalah
-1, semua terjemahan akan diterapkan dalam arah yang berlawanan. Jadi, bagaimana cara melakukannya? Mesin CSS akan menangani konversi sudut kotak menjadi
koordinat homogen, dan menetapkan w ke 1. Saatnya matrix3d()
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 meniadakan w. Jadi, saat mesin CSS telah
mengubah setiap sudut menjadi vektor dalam bentuk [x,y,z,1]
, matriks akan
mengonversinya menjadi [x,y,z,-1]
.
Saya mencantumkan langkah perantara untuk menunjukkan efek matriks transformasi elemen kita. Jika Anda tidak nyaman dengan matematika matriks, tidak apa-apa. Momen Eureka adalah bahwa di baris terakhir, kita akhirnya menambahkan offset scroll n ke koordinat y, bukan menguranginya. Elemen akan diterjemahkan ke bawah jika kita men-scroll ke bawah.
Namun, jika kita hanya menempatkan matriks ini dalam contoh, elemen tidak akan ditampilkan. Hal ini karena spesifikasi CSS mewajibkan setiap vertikal dengan w < 0 memblokir elemen agar tidak dirender. Dan karena koordinat z saat ini adalah 0, dan p adalah 1, w akan menjadi -1.
Untungnya, kita dapat memilih nilai z. Untuk memastikan kita mendapatkan w=1, kita perlu menetapkan z = -2.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
Lihat, kotak kami kembali.
Langkah 2: Buat bergerak
Sekarang kotak kita ada dan terlihat sama seperti tanpa transformasi. Saat ini, penampung perspektif tidak dapat di-scroll, sehingga kita tidak dapat melihatnya, tetapi kita tahu bahwa elemen kita akan mengarah ke arah lain saat di-scroll. Jadi, mari kita buat penampung di-scroll. Kita cukup menambahkan elemen pengatur jarak yang menggunakan 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>
Sekarang, scroll kotak. Kotak merah akan bergerak ke bawah.
Langkah 3: Berikan ukuran
Kita memiliki elemen yang bergerak ke bawah saat halaman di-scroll ke bawah. Itu adalah bagian yang sulit. Sekarang kita perlu menata gayanya agar terlihat seperti scrollbar dan membuatnya sedikit lebih interaktif.
Scrollbar biasanya terdiri dari “thumb” dan “track”, sedangkan track tidak selalu terlihat. Tinggi thumb sebanding dengan jumlah konten yang terlihat.
<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 fraksi konten yang
terlihat. Rasio ruang vertikal yang ditutupi thumb 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 terlihat bagus, tetapi bergerak terlalu cepat. Di sinilah kita dapat mengambil teknik dari parallax scroller. Jika kita memindahkan elemen lebih jauh ke belakang, elemen tersebut akan bergerak lebih lambat saat men-scroll. Kita dapat memperbaiki ukurannya dengan menskalakannya. Namun, berapa banyak kita harus mendorongnya kembali? Mari kita lakukan beberapa – tebak – matematika! Ini adalah yang terakhir, saya janji.
Informasi penting adalah kita ingin tepi bawah ibu jari
sejajar dengan tepi bawah elemen yang dapat di-scroll saat di-scroll hingga
ke bawah. Dengan kata lain: Jika kita telah men-scroll
scroller.scrollHeight - scroller.height
piksel, kita ingin ibu jari kita
diterjemahkan oleh scroller.height - thumb.height
. Untuk setiap piksel penggeser, kita
ingin ibu jari kita memindahkan sebagian piksel:
Itulah faktor penskalaan kita. Sekarang kita perlu mengonversi faktor penskalaan menjadi
terjemahan di sepanjang sumbu z, yang telah kita lakukan dalam artikel scrolling
paralaks. Menurut
bagian yang relevan dalam spesifikasi:
Faktor penskalaan sama dengan p/(p − z). Kita dapat menyelesaikan persamaan ini untuk z guna
mengetahui berapa banyak kita perlu menerjemahkan ibu jari kita di sepanjang sumbu z. Namun, perlu
diingat bahwa karena trik koordinat w, kita perlu menerjemahkan
-2px
tambahan di sepanjang z. Perhatikan juga bahwa transformasi elemen diterapkan
dari kanan ke kiri, yang berarti semua terjemahan sebelum matriks khusus kita tidak akan
dibalik, tetapi semua terjemahan setelah matriks khusus kita akan dibalik. Mari kita
kodifikasi.
<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>
Kita memiliki scrollbar. Dan ini hanyalah elemen DOM yang dapat kita tata gayanya sesuka kita. Satu hal yang penting untuk dilakukan dalam hal aksesibilitas adalah membuat ibu jari merespons klik-dan-tarik, karena banyak pengguna terbiasa berinteraksi dengan scrollbar dengan cara tersebut. Agar postingan blog ini tidak menjadi lebih panjang, saya tidak akan menjelaskan detail untuk bagian tersebut. Lihat kode library untuk mengetahui detailnya jika Anda ingin melihat cara melakukannya.
Bagaimana dengan iOS?
Ah, teman lama saya, Safari iOS. Seperti halnya scroll paralaks, kita mengalami
masalah di sini. Karena kita men-scroll pada elemen, kita perlu menentukan
-webkit-overflow-scrolling: touch
, tetapi hal itu menyebabkan perataan 3D dan seluruh
efek scroll kita berhenti berfungsi. Kami telah menyelesaikan masalah ini di penggeser paralaks
dengan mendeteksi Safari iOS dan mengandalkan position: sticky
sebagai solusi, dan
kami akan melakukan hal yang sama persis di sini. Lihat
artikel paralaks
untuk menyegarkan ingatan Anda.
Bagaimana dengan scrollbar browser?
Di beberapa sistem, kita harus menangani scrollbar native permanen.
Secara historis, scrollbar tidak dapat disembunyikan (kecuali dengan
pseudo-pemilih non-standar).
Jadi, untuk menyembunyikannya, kita harus menggunakan beberapa teknik peretasan (tanpa matematika). Kita menggabungkan
elemen scroll dalam penampung dengan overflow-x: hidden
dan membuat
elemen scroll lebih lebar dari penampung. Scrollbar native browser
kini tidak terlihat.
Sirip
Dengan menggabungkan semuanya, kini kita dapat membuat scrollbar kustom yang sempurna untuk frame – seperti yang ada di demo Nyan cat.
Jika tidak dapat melihat Nyan cat, Anda mengalami bug yang kami temukan dan laporkan saat mem-build demo ini (klik jempol untuk membuat Nyan cat muncul). Chrome sangat baik dalam menghindari pekerjaan yang tidak perlu seperti menggambar atau menganimasikan hal-hal yang berada di luar layar. Kabar buruknya adalah kelakuan aneh matriks kami membuat Chrome mengira gif Nyan cat sebenarnya berada di luar layar. Semoga masalah ini segera teratasi.
Seperti itulah. Itu adalah pekerjaan yang berat. Saya memuji Anda karena telah membaca semuanya. Ini adalah trik yang benar-benar rumit untuk membuatnya berfungsi dan mungkin jarang sepadan dengan upayanya, kecuali jika scrollbar yang disesuaikan adalah bagian penting dari pengalaman. Namun, bagus untuk mengetahui bahwa hal itu mungkin, bukan? Fakta bahwa scrollbar kustom begitu sulit dilakukan menunjukkan bahwa ada pekerjaan yang harus dilakukan di sisi CSS. Namun, jangan khawatir. Di masa mendatang, AnimationWorklet Houdini akan membuat efek scroll-linked frame-perfect seperti ini jauh lebih mudah.