Di dalam polyfill kueri container

Gerald Monaco
Gerald Monaco

Kueri penampung adalah fitur CSS baru yang memungkinkan Anda menulis logika gaya visual yang menargetkan fitur elemen induk (misalnya, lebar atau tingginya) untuk menata gaya turunannya. Baru-baru ini, pembaruan besar untuk polyfill dirilis, bertepatan dengan masuknya dukungan di browser.

Pada postingan ini, Anda akan dapat mengintip cara kerja polyfill, tantangan yang diatasi, dan praktik terbaik saat menggunakannya untuk memberikan pengalaman pengguna yang luar biasa bagi pengunjung Anda.

Di balik layar

Transpilasi

Saat parser CSS di dalam browser menemukan aturan yang tidak dikenal, seperti aturan @container baru, parser hanya akan membuangnya seolah-olah tidak pernah ada. Oleh karena itu, hal pertama dan terpenting yang harus dilakukan polyfill adalah mentranspilasi kueri @container menjadi sesuatu yang tidak akan dihapus.

Langkah pertama dalam transpilasi adalah mengonversi aturan @container tingkat atas menjadi kueri @media. Cara ini terutama memastikan bahwa konten tetap dikelompokkan bersama. Misalnya, saat menggunakan CSSOM API dan saat melihat sumber CSS.

Sebelum
@container (width > 300px) {
  /* content */
}
Setelah
@media all {
  /* content */
}

Sebelum kueri penampung, CSS tidak menyediakan cara bagi penulis untuk mengaktifkan atau menonaktifkan grup aturan secara bebas. Untuk mem-polyfill perilaku ini, aturan di dalam kueri container juga perlu diubah. Setiap @container diberi ID uniknya sendiri (misalnya, 123), yang digunakan untuk mengubah setiap pemilih sehingga hanya akan diterapkan saat elemen memiliki atribut cq-XYZ termasuk ID ini. Atribut ini akan ditetapkan oleh polyfill saat runtime.

Sebelum
@container (width > 300px) {
  .card {
    /* ... */
  }
}
Setelah
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

Perhatikan penggunaan class pseudo :where(...). Biasanya, menyertakan pemilih atribut tambahan akan meningkatkan kekhususan pemilih. Dengan class pseudo, kondisi tambahan dapat diterapkan sekaligus mempertahankan kekhususan asli. Untuk memahami mengapa hal ini sangat penting, pertimbangkan contoh berikut:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

Dengan CSS ini, elemen dengan class .card harus selalu memiliki color: red, karena aturan berikutnya akan selalu mengganti aturan sebelumnya dengan pemilih dan kekhususan yang sama. Transpilasi aturan pertama dan menyertakan pemilih atribut tambahan tanpa :where(...) akan meningkatkan kekhususan, dan menyebabkan color: blue diterapkan secara salah.

Namun, class pseudo :where(...) cukup baru. Untuk browser yang tidak mendukungnya, polyfill menyediakan solusi yang aman dan mudah: Anda dapat sengaja meningkatkan kekhususan aturan dengan menambahkan pemilih :not(.container-query-polyfill) tiruan ke aturan @container secara manual:

Sebelum
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
Setelah
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

Hal ini memiliki sejumlah manfaat:

  • Pemilih di CSS sumber telah berubah, sehingga perbedaan kekhususan terlihat secara eksplisit. Ini juga bertindak sebagai dokumentasi sehingga Anda tahu apa yang terpengaruh bila Anda tidak perlu lagi mendukung solusi atau polyfill.
  • Kekhususan aturan akan selalu sama, karena polyfill tidak berubah.

Selama transpilasi, polyfill akan mengganti dummy ini dengan pemilih atribut dengan kekhususan yang sama. Untuk menghindari hal yang tidak diinginkan, polyfill menggunakan kedua pemilih: pemilih sumber asli digunakan untuk menentukan apakah elemen harus menerima atribut polyfill, dan pemilih yang ditranspilasi akan digunakan untuk penataan gaya.

Elemen Pseudo

Satu pertanyaan yang mungkin Anda tanyakan pada diri sendiri adalah: jika polyfill menetapkan beberapa atribut cq-XYZ pada elemen untuk menyertakan ID penampung unik 123, bagaimana elemen pseudo, yang tidak dapat memiliki atribut yang ditetapkan, didukung?

Elemen Pseudo selalu terikat ke elemen nyata dalam DOM, yang disebut elemen asal. Selama transpilasi, pemilih kondisional diterapkan ke elemen sebenarnya ini:

Sebelum
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
Setelah
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

Alih-alih diubah menjadi #foo::before:where([cq-XYZ~="123"]) (yang akan menjadi tidak valid), pemilih kondisional dipindahkan ke akhir elemen asal, #foo.

Namun, bukan hanya itu yang diperlukan. Penampung tidak diizinkan untuk mengubah apa pun yang tidak berada di dalam di dalamnya (dan penampung tidak dapat berada di dalam penampung itu sendiri), tetapi pertimbangkan hal itulah yang akan terjadi jika #foo merupakan elemen penampung yang dikueri. Atribut #foo[cq-XYZ] akan diubah secara keliru, dan aturan #foo akan diterapkan secara salah.

Untuk memperbaikinya, polyfill sebenarnya menggunakan dua atribut: satu yang hanya dapat diterapkan pada elemen oleh induk, dan satu lagi yang dapat diterapkan oleh elemen pada elemen itu sendiri. Atribut yang terakhir digunakan untuk pemilih yang menargetkan elemen pseudo.

Sebelum
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
Setelah
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

Karena penampung tidak akan pernah menerapkan atribut pertama (cq-XYZ-A) ke penampungnya sendiri, pemilih pertama hanya akan cocok jika penampung induk yang berbeda telah memenuhi kondisi penampung dan menerapkannya.

Unit relatif container

Kueri penampung juga dilengkapi dengan beberapa unit baru yang dapat Anda gunakan di CSS, seperti cqw dan cqh untuk masing-masing lebar dan tinggi 1% dari penampung induk terdekat yang sesuai. Untuk mendukung hal ini, unit diubah menjadi ekspresi calc(...) menggunakan Properti Kustom CSS. Polyfill akan menetapkan nilai untuk properti ini melalui gaya inline pada elemen penampung.

Sebelum
.card {
  width: 10cqw;
  height: 10cqh;
}
Setelah
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

Ada juga unit logis, seperti cqi dan cqb untuk ukuran inline dan ukuran blok (masing-masing). Hal ini sedikit lebih rumit, karena sumbu inline dan blok ditentukan oleh writing-mode elemen menggunakan unit, bukan elemen yang dikueri. Untuk mendukung hal ini, polyfill menerapkan gaya inline ke elemen apa pun yang writing-mode-nya berbeda dari induknya.

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

Sekarang, unit tersebut dapat diubah menjadi Properti Khusus CSS yang sesuai seperti sebelumnya.

Properti

Kueri penampung juga menambahkan beberapa properti CSS baru seperti container-type dan container-name. Karena API seperti getComputedStyle(...) tidak dapat digunakan dengan properti yang tidak dikenal atau tidak valid, properti ini juga akan diubah menjadi Properti Khusus CSS setelah diuraikan. Jika properti tidak dapat diurai (misalnya, karena berisi nilai yang tidak valid atau tidak diketahui), properti tersebut akan ditangani oleh browser.

Sebelum
.card {
  container-name: card-container;
  container-type: inline-size;
}
Setelah
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

Properti ini akan diubah setiap kali ditemukan, sehingga polyfill dapat berjalan dengan baik dengan fitur CSS lainnya seperti @supports. Fungsi ini adalah dasar praktik terbaik untuk menggunakan polyfill, seperti yang dibahas di bawah.

Sebelum
@supports (container-type: inline-size) {
  /* ... */
}
Setelah
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

Secara default, Properti Khusus CSS diwarisi, artinya, misalnya, setiap turunan .card akan menggunakan nilai --cq-XYZ-container-name dan --cq-XYZ-container-type. Perilaku properti native ini tidak sama persis. Untuk mengatasi ini, polyfill akan menyisipkan aturan berikut sebelum gaya pengguna apa pun, memastikan bahwa setiap elemen menerima nilai awal, kecuali jika sengaja diganti oleh aturan lain.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

Praktik terbaik

Meskipun sebagian besar pengunjung diperkirakan akan menjalankan browser dengan dukungan kueri penampung bawaan lebih cepat, Anda tetap harus memberikan pengalaman yang baik kepada pengunjung yang tersisa.

Selama pemuatan awal, ada banyak hal yang perlu terjadi sebelum polyfill dapat mengatur tata letak halaman:

  • Polyfill harus dimuat dan diinisialisasi.
  • Stylesheet perlu diuraikan dan ditranspilasi. Karena tidak ada API untuk mengakses sumber mentah stylesheet eksternal, API mungkin perlu diambil ulang secara asinkron, meskipun idealnya hanya dari cache browser.

Jika masalah ini tidak ditangani dengan cermat oleh polyfill, Data Web Inti Anda dapat mengalami regresi.

Untuk memudahkan Anda dalam memberikan pengalaman yang menyenangkan kepada pengunjung, polyfill dirancang untuk memprioritaskan Penundaan Input Pertama (FID) dan Pergeseran Tata Letak Kumulatif (CLS), yang mungkin mengorbankan Largest Contentful Paint (LCP). Sebenarnya, polyfill tidak menjamin bahwa kueri container Anda akan dievaluasi sebelum first paint. Artinya, demi pengalaman pengguna terbaik, Anda harus memastikan bahwa konten apa pun yang ukuran atau posisinya akan terpengaruh oleh penggunaan kueri container disembunyikan hingga polyfill dimuat dan ditranspilasi oleh CSS Anda. Salah satu cara untuk melakukannya adalah dengan menggunakan aturan @supports:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

Sebaiknya gabungkan proses ini dengan animasi pemuatan CSS murni, yang diposisikan secara benar di atas konten (tersembunyi), untuk memberi tahu pengunjung bahwa sesuatu sedang terjadi. Anda dapat menemukan demo lengkap pendekatan ini di sini.

Pendekatan ini direkomendasikan karena sejumlah alasan:

  • Loader CSS murni meminimalkan beban bagi pengguna dengan browser yang lebih baru, sekaligus memberikan umpan balik ringan untuk pengguna yang menggunakan browser lama dan jaringan yang lebih lambat.
  • Dengan menggabungkan pemosisian mutlak loader dengan visibility: hidden, Anda akan menghindari pergeseran tata letak.
  • Setelah polyfill dimuat, kondisi @supports ini akan berhenti diteruskan, dan konten Anda akan ditampilkan.
  • Pada browser yang memiliki dukungan bawaan untuk kueri container, kondisi tersebut tidak akan pernah diteruskan, sehingga halaman akan ditampilkan pada gambar pertama seperti yang diharapkan.

Kesimpulan

Jika Anda tertarik menggunakan kueri container di browser lama, coba polyfill. Jangan ragu untuk mengajukan masalah jika Anda mengalami masalah.

Kami tidak sabar ingin melihat dan merasakan hal-hal menakjubkan yang akan Anda bangun dengan platform ini.

Ucapan terima kasih

Banner besar oleh Dan Cristian Pădurebert di Unsplash.