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 membangun kode Anda sendiri, tetapi itu mahal, memiliki fidelitas rendah, dan 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; tetap akan dibaca)
Beberapa waktu yang lalu, kami membuat scroller paralaks (Apakah Anda membaca artikel tersebut? Hasilnya sangat baik dan 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 rangkuman cara kerja scroller paralaks.
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 adalah 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?
Scrollbar. Itulah yang akan kita bangun. Tetapi 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 kami bisa dan begitulah cara kami membuat scrollbar kustom yang sempurna untuk frame. 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 detail tentang apa itu dan mengapa 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 mengkhawatirkan detail w karena kita tidak akan menggunakan nilai lain 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 dalam
ruang 3D yang baru dibuat. Contoh
contoh:
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 membuatnya “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 berlaku 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 kita 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: Bergeraklah
Sekarang kotak kita ada di sana dan terlihat sama seperti tanpa transformasi. 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. Jadi, mari kita buat penampung di-scroll. Kita cukup menambahkan elemen {i>spacer <i}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>
Sekarang, scroll kotak. Kotak merah akan bergerak ke bawah.
Langkah 3: Berikan ukuran
Kita memiliki elemen yang bergerak ke bawah ketika 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 dicakup 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 terlihat bagus, tetapi bergerak terlalu cepat. Di sinilah kita dapat mengambil teknik dari parallax scroller. Jika kita memindahkan elemen lebih jauh ke belakang, elemen akan bergerak lebih lambat saat men-scroll. Kita dapat memperbaiki ukurannya dengan menskalakannya. Namun, berapa banyak kita harus mendorongnya kembali? Ayo mulai 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 kami. 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 seberapa 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 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-tarik, karena banyak pengguna terbiasa berinteraksi dengan scrollbar dengan cara tersebut. Agar postingan blog ini tidak bertele-tele, saya tidak akan menjelaskan detailnya 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 men-scroll pada elemen, kita perlu menetapkan
-webkit-overflow-scrolling: touch
, tetapi tindakan ini akan menyebabkan perataan 3D dan seluruh
efek scroll berhenti berfungsi. Kita telah menyelesaikan masalah ini di scroller paralaks
dengan mendeteksi iOS Safari dan mengandalkan position: sticky
sebagai solusinya, dan
kita akan melakukan hal yang sama persis di sini. Lihat
artikel paralaks
untuk mengingat kembali.
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 melakukan beberapa peretas (bebas 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 itu. 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 upaya yang dilakukan, kecuali jika scrollbar yang disesuaikan adalah bagian penting dari pengalaman. Tapi senang rasanya mengetahui bahwa 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.