Fitur-fitur baru tidak hanya muncul di CSS (tapi saya berharap demikian). Sebaliknya, mereka melalui proses diskusi dan pertimbangan yang ekstensif, definisi, penulisan, pembuatan prototipe, pengujian, dukungan, penanganan pengiriman, dan banyak tindakan lainnya yang bahkan tidak dapat saya bayangkan. Proses itu panjangMeskipun saya sangat menginginkan fitur baru, sebagai pengembang sehari-hari, saya tidak bisa menahan diri untuk menunggu.
Namun, saya dapat mengontrol cara saya menunggu: Bisakah saya menghindari semua kemungkinan antarmuka atau demo dengan fitur yang satu ini? Atau apakah saya melampaui batas CSS dan tetap mencoba melakukannya?
Sebagai pengembang yang ambisius dan penuh rasa ingin tahu, banyak dari kita memilih yang terakhir. CSS akan menjadi stagnan tanpa pola pikir ini. Oleh karena itu, hari ini saya ingin melihat dua postingan mendatang: sibling-count()
Dan sibling-index()
. Kami telah menunggu mereka – selama bertahun-tahun – jadi saya membiarkan keingintahuan alami menguasai diri saya sehingga saya bisa merasakan apa yang membuat saya bergairah. Bergabunglah dengan saya!
-Advertisement-.
Pekerjaan menghitung pohon
Pada titik tertentu, Anda mungkin ingin mengetahui posisi suatu elemen di antara saudara kandungnya atau jumlah anak suatu elemen untuk menghitung sesuatu dalam CSS, mungkin untuk beberapa animasi luar biasa di mana setiap elemen memiliki penundaan lebih lama, atau mungkin untuk mengubah sebuah elemen background-color
Sesuai dengan jumlah saudara-saudaranya. Ini adalah kesepakatan yang sudah lama ditunggu-tunggu di daftar keinginan CSS saya. Ambil rilis GitHub CSSWG dari tahun 2017:
Permintaan fitur. Akan menyenangkan jika bisa digunakan
counter()
berfungsi di dalamcalc()
pekerjaan. Ini akan membuka kemungkinan baru terkait tata letak.
Namun, penghitung beroperasi menggunakan string, menjadikannya tidak berguna di dalam file calc()
Sebuah fungsi yang berhubungan dengan angka. Kita memerlukan sekumpulan fungsi serupa yang kembali Sebagai bilangan bulat Indeks item dan jumlah saudara kandung. Sepertinya tidak banyak yang perlu ditanyakan. Saat ini kita dapat menanyakan suatu elemen berdasarkan posisi pohonnya menggunakan fungsi tersebut :nth-child()
Penentu semu (dan variabelnya), apalagi menanyakan suatu elemen berdasarkan jumlah elemen di dalamnya menggunakan :has()
Penentu yang salah.
Untungnya, tahun ini CSSWG setuju untuk menerapkannya sibling-count()
Dan sibling-index()
Pekerjaan! Dan kami sudah memiliki sesuatu yang tertulis di spesifikasi:
itu
sibling-count()
Notasi fungsional mewakili, sebagai<integer>
jumlah total elemen anak di induk elemen yang pengkodeannya digunakan.itu
sibling-index()
Notasi fungsional mewakili, sebagai<integer>
indeks item yang notasinya digunakan di antara putra ayahnya. Dia mencintai:nth-child()
,sibling-index()
Ini adalah 1 yang diindeks.
Berapa lama kita harus menunggu untuk menggunakannya? Awal tahun ini, Adam Argyle mengatakan bahwa “salah satu insinyur Chromium menyebutkan ingin melakukan ini, namun kami belum memiliki ilmu untuk mencobanya. Saya akan membagikannya jika kami sudah memilikinya!” berita di tahun 2025, kita mungkin tidak akan melihatnya dikirimkan dalam waktu dekat, jadi mari kita lakukan apa yang bisa kita lakukan sekarang!
Gosokkan dua batang kayu menjadi satu
Cara terdekat yang bisa kita dapatkan untuk fungsi penghitungan pohon dalam hal sintaksis dan penggunaan adalah dengan menggunakan properti khusus. Namun masalah terbesarnya adalah mengisinya dengan indeks dan integer. Cara paling sederhana dan terpanjang adalah dengan melakukan hard-code masing-masing hanya dengan CSS: kita dapat menggunakan a nth-child()
Selector untuk memberikan setiap elemen indeks yang sesuai:
li:nth-child(1) {
--sibling-index: 1;
}
li:nth-child(2) {
--sibling-index: 2;
}
li:nth-child(3) {
--sibling-index: 3;
}
/* and so on... */
angka sibling-count()
Persamaannya memiliki perbedaan karena kita perlu menggunakan kueri kuantitas :has()
spesifik. Kueri kuantitas memiliki sintaks berikut:
.container:has(> :last-child:nth-child(m)) { }
…Di mana m
Ini adalah jumlah item yang ingin kami targetkan. Ia bekerja dengan memeriksa apakah elemen terakhir dalam wadah juga ada nth
Item yang kami targetkan; Oleh karena itu, ia hanya berisi sejumlah elemen ini. Anda dapat membuat kueri kuantitas khusus Anda sendiri menggunakan alat dari Temani Afif ini. Dalam hal ini, kueri kuantitatif kami akan terlihat seperti ini:
ol:has(> :nth-child(1)) {
--sibling-count: 1;
}
ol:has(> :last-child:nth-child(2)) {
--sibling-count: 2;
}
ol:has(> :last-child:nth-child(3)) {
--sibling-count: 3;
}
/* and so on... */
Contoh ini sengaja menyoroti jumlah item agar singkatnya, namun seiring bertambahnya daftar, daftar tersebut menjadi tidak dapat dikelola. Kami mungkin dapat menggunakan praprosesor seperti Sass untuk menulisnya untuk kami, namun kami ingin fokus pada solusi CSS sederhana di sini. Misalnya, demo berikut dapat mendukung hingga 12 objek, dan Anda sudah dapat melihat betapa jeleknya kode tersebut.
Ini adalah 24 aturan untuk mengetahui indeks dan 12 item untuk Anda yang mencatat skor. Sepertinya kita bisa menurunkan angka tersebut menjadi sesuatu yang lebih mudah dikelola, namun jika kita mengkodekan setiap indeks, kita pasti akan menambah jumlah kode yang kita tulis. Hal terbaik yang bisa kita lakukan adalah menulis ulang CSS kita sehingga kita bisa membuat sarangnya --sibling-index
Dan --sibling-count
properti bersama-sama. Daripada menulis setiap properti satu per satu:
li:nth-child(2) {
--sibling-index: 2;
}
ol:has(> :last-child:nth-child(2)) {
--sibling-count: 2;
}
Kita malah bisa tumpang tindih --sibling-count
Aturan di dalam --sibling-index
sebuah pangkalan.
li:nth-child(2) {
--sibling-index: 2;
ol:has(> &:last-child) {
--sibling-count: 2;
}
}
Meskipun kelihatannya konyol untuk menyematkan induk ke dalam anaknya, kode CSS berikut ini benar-benar valid; Kami memilih yang kedua li
elemen, dan di dalamnya, kita membuat pilihan ol
Elemen jika yang kedua li
Item tersebut juga merupakan item terakhir, jadi daftarnya hanya berisi dua item. Sintaks mana yang paling mudah dikelola? Terserah kamu.
Namun ini hanya sedikit perbaikan. Jika kita mempunyai, misalnya, 100 item, kita masih perlu menyandikannya --sibling-index
Dan --sibling-count
Properti 100 kali. Untungnya, metode berikut ini akan menambah aturan secara logaritmik, khususnya basis 2. Jadi, daripada menulis 100 aturan untuk 100 elemen, kita akan menulis sekitar 10 aturan untuk sekitar 100 elemen.
Batu api dan baja
Metode ini pertama kali dijelaskan oleh Roman Komarov pada bulan Oktober tahun lalu, di mana ia mempresentasikan prototipe penghitungan pohon dan fungsi masa depan random()
pekerjaan. Ini postingan yang bagus, jadi saya sangat menganjurkan Anda untuk membacanya.
Metode ini juga menggunakan properti khusus, namun alih-alih mengkodekan masing-masing properti secara keras, kita akan menggunakan dua properti khusus yang akan membuat file --sibling-index
Properti untuk setiap elemen. Agar konsisten dengan postingan Roman, kami akan menghubungi mereka --si1
Dan --si2
keduanya dimulai dari 0
:
li {
--si1: 0;
--si2: 0;
}
Nyata --sibling-index
Mereka akan dibangun menggunakan keduanya dan properti faktor (F
) mewakili bilangan bulat yang lebih besar dari atau sama dengan 2
Ini memberi tahu kita berapa banyak elemen yang dapat kita identifikasi berdasarkan rumus sqrt(F) - 1
. Jadi…
- Untuk seorang pekerja
2
kita bisa memilih3
Elemen. - Untuk seorang pekerja
3
kita bisa memilih8
Elemen. - Untuk seorang pekerja
5
kita bisa memilih24
Elemen. - Untuk seorang pekerja
10
kita bisa memilih99
Elemen. - Untuk seorang pekerja
25
kita bisa memilih624
Elemen.
Seperti yang bisa Anda lihat, menambah faktor sebanyak satu akan memberi kita keuntungan eksponensial dalam hal jumlah elemen yang bisa kita pilih. Tapi bagaimana semua ini diterjemahkan ke dalam CSS?
Hal pertama yang harus diketahui adalah rumus perhitungannya --sibling-index
Kepemilikan adalah calc(F * var(--si2) + var(--si1))
. Jika kita mengambil sebuah faktor 3
akan terlihat seperti ini:
li {
--si1: 0;
--si2: 0;
/* factor of 3; it's a harcoded number */
--sibling-index: calc(3 * var(--si2) + var(--si1));
}
Parameter berikut mungkin acak tetapi tetap bersama saya di sini. ke --si1
Properti, kita akan menulis aturan untuk memilih elemen yang merupakan kelipatan faktor dan mengembalikannya ke satu 1
Sampai kita tiba F - 1
lalu sesuaikan --si1
Untuk perpindahan. Ini diterjemahkan ke CSS berikut:
li:nth-child(Fn + 1) { --si1: 1; }
li:nth-child(Fn + 2) { --si1: 2; }
/* ... */
li:nth-child(Fn+(F-1)) { --si1: (F-1) }
Jadi jika pekerja kita adalah 3
kami akan menulis aturan berikut sampai kami tiba F-1
Jadi 2
aturan:
li:nth-child(3n + 1) { --si1: 1; }
li:nth-child(3n + 2) { --si1: 2; }
ke --si2
Properti, kami akan menulis aturan untuk memilih item dalam batch pekerja (jadi jika pekerja kami adalah 3
kami akan memilih 3
elemen untuk setiap aturan), dimulai dari indeks terakhir yang mungkin (dalam hal ini 8
) mundur sehingga kita tidak bisa begitu saja memilih lebih banyak item secara berkelompok. Ini lebih rumit ketika menulis dalam CSS:
li:nth-child(n + F*1):nth-child(-n + F*1-1){--si2: 1;}
li:nth-child(n + F*2):nth-child(-n + F*2-1){--si2: 2;}
/* ... */
li:nth-child(n+(F*(F-1))):nth-child(-n+(F*F-1)) { --si2: (F-1) }
Sekali lagi, jika pekerja kita adalah 3
Kami akan menulis dua aturan berikut:
li:nth-child(n + 3):nth-child(-n + 5) {
--si2: 1;
}
li:nth-child(n + 6):nth-child(-n + 8) {
--si2: 2;
}
Dan itu saja! Dengan menetapkan hanya dua nilai ini --si1
Dan --si2
Kami dapat mengandalkannya 8
Jumlah item. Perhitungan di balik cara kerjanya tampak aneh pada awalnya, tetapi setelah Anda memahaminya secara visual, itu berhasil. Saya telah membuat demo interaktif di mana Anda dapat melihat bagaimana semua elemen dapat diakses menggunakan rumus ini. Arahkan kursor ke cuplikan kode untuk melihat item mana yang dapat dipilih, lalu klik setiap cuplikan untuk menggabungkannya ke dalam indeks yang memungkinkan.
Jika saya memindahkan item dan faktornya ke maksimal, Anda dapat melihat bahwa kami dapat memilih 48 item hanya dengan menggunakan 14 cuplikan!
Tunggu, ada satu hal yang hilang: sibling-count()
pekerjaan. Untungnya, kami akan menggunakan kembali semua yang kami pelajari dari prototipe --sibling-index
. Kita akan mulai dengan dua properti khusus: --sc1
Dan --sc1
Di dalam wadah, keduanya memulai dari 0
Juga. Rumus perhitungan --sibling-count
diri.
ol {
--sc1: 0;
--sc2: 0;
/* factor of 3; also a harcoded number */
--sibling-count: calc(3 * var(--sc2) + var(--sc1));
}
Postingan Roman juga menjelaskan cara menulis penyeleksi --sibling-count
Properti itu sendiri, tapi akan kami gunakan :has()
Cara pemilihannya adalah dari cara pertama kita sehingga kita tidak perlu menulis penyeleksi tambahan. Kita bisa menjejalkannya --sc1
Dan --sc2
Properti dalam aturan tempat kita menentukan sibling-index()
Properti:
/* --si1 and --sc1 */
li:nth-child(3n + 1) {
--si1: 1;
ol:has(> &:last-child) {
--sc1: 1;
}
}
li:nth-child(3n + 2) {
--si1: 2;
ol:has(> &:last-child) {
--sc1: 2;
}
}
/* --si2 and --sc2 */
li:nth-child(n + 3):nth-child(-n + 5) {
--si2: 1;
ol:has(> &:last-child) {
--sc2: 1;
}
}
li:nth-child(n + 6):nth-child(-n + 8) {
--si2: 2;
ol:has(> &:last-child) {
--sc2: 2;
}
}
Ini menggunakan operator 3
sehingga kita dapat menghitung hingga delapan elemen dengan hanya empat basis. Contoh berikut memiliki faktor 7
jadi kita bisa menghitung hingga 48 elemen hanya dengan 14 aturan.
Metode ini bagus, tetapi mungkin bukan yang terbaik untuk semua orang karena cara kerjanya yang ajaib, atau hanya karena menurut Anda metode ini tidak estetis. Meskipun menyalakan api dengan batu api dan baja mudah dilakukan oleh tangan yang haus, banyak yang tidak mau menyalakan api.
Menggunakan penyembur api
Dalam metode ini, kita akan kembali menggunakan properti khusus untuk meniru fungsi penghitungan pohon, dan lebih baik lagi, kita akan menulis kurang dari 20 baris kode untuk menghitung hingga tak terbatas – atau menurut saya begitu 1.7976931348623157e+308
yang merupakan batas floating point presisi ganda!
Kami akan menggunakan Mutation Observer API, jadi tentu saja memerlukan JavaScript. Saya tahu ini terdengar seperti mengakui kekalahan bagi banyak orang, tapi saya tidak setuju. Jika metode JavaScript lebih sederhana (dan dalam hal ini memang lebih sederhana), itulah cara yang tepat. Sebagai catatan tambahan, jika kinerja adalah perhatian utama Anda, pertahankan pengkodean keras pada setiap indeks dalam CSS atau HTML.
Pertama, kita akan mengambil container kita dari DOM:
const elements = document.querySelector("ol");
Kemudian kita akan membuat fungsi yang mendefinisikan --sibling-index
Properti di setiap elemen adalah --sibling-count
Di dalam wadah (dapat diakses oleh anak-anaknya karena adanya air terjun). ke --sibling-index
Kita harus terbang melewatinya elements.children
Dan kita bisa mendapatkannya --sibling-count
dari elements.children.length
.
const updateCustomProperties = () => {
let index = 1;
for (element of elements.children) {
element.style.setProperty("--sibling-index", index);
index++;
}
elements.style.setProperty("--sibling-count", elements.children.length);
};
Setelah kita memiliki fungsinya, ingatlah untuk memanggilnya sekali sehingga kita mendapatkan properti awal untuk menghitung pohon:
updateCustomProperties();
Terakhir, monitor lonjakan arus. Kita perlu memulai penggunaan pengamat baru MutationObserver
Konstruktor. Ini memerlukan panggilan balik yang dipanggil setiap kali elemen berubah, jadi kami menulis propertinya updateCustomProperties
pekerjaan. Dengan hasilnya observer
Objeknya, kita bisa menyebutnya observe()
Metode ini mengambil dua parameter:
- Item yang ingin kami pantau, dan
- A
config
Sebuah objek mendefinisikan apa yang ingin kita amati melalui tiga properti logis:attributes
,childList
Dansubtree
. Dalam hal ini, kami hanya ingin memeriksa perubahan pada submenu, jadi kami setel ketrue
:
const observer = new MutationObserver(updateCustomProperties);
const config = {attributes: false, childList: true, subtree: false};
observer.observe(elements, config);
Hanya itu yang kami butuhkan! Dengan menggunakan metode ini kita dapat menghitung banyak item, pada demo berikut saya atur maksimal 100
Tapi itu bisa dengan mudah menjadi sepuluh kali lipat:
Jadi, ya, itulah penyembur api kami di sana. Hal ini tentu saja memicu kebakaran, tetapi hal ini berlebihan untuk sebagian besar kasus penggunaan. Tapi inilah yang kita miliki sementara kita menunggu pemantik api yang sempurna.
Informasi dan tutorial lebih lanjut
- Kemungkinan masa depan CSS: fungsi penghitungan pohon dan nilai acak (Roman Komarov)
- Pertunjukan Transformasi yang Menakjubkan (Chris Cowher)
- Katalog barang (Chris Cowher)
Masalah terkait
- Aktifkan penggunaan
counter()
di dalamcalc()
#1026 - Saran: Tambahkan
sibling-count()
Dansibling-index()
#4559 - meluas
sibling-index()
Dansibling-count()
Dengan argumen tertentu #9572 - sebuah tawaran:
children-count()
Pekerjaan No.11068 - sebuah tawaran:
descendant-count()
Pekerjaan No.11069
Cara menunggu fungsi sibling-count() dan sibling-index() awalnya diterbitkan di CSS-Tricks, yang merupakan bagian dari keluarga DigitalOcean. Anda harus mendapatkan buletin.