Header melekat adalah pola UI yang umum, tetapi sering kali perlu mengubah tampilannya saat menjadi "melekat" untuk mempertahankan keterbacaan atau menghemat ruang. Solusi tradisional memerlukan pemroses scroll JavaScript, yang dapat menyebabkan masalah performa. CSS modern memungkinkan Anda menangani hal ini secara native menggunakan Kueri Status Scroll.
Langkah-Langkah Penerapan
1. Membuat Penampung Tetap
Anda memerlukan container yang akan bertindak sebagai elemen melekat dan container untuk kueri status scroll.
<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
<div class="sticky-container">
<div class="sticky-header">
Section Header
</div>
</div>
<div class="content">
<!-- Content goes here -->
</div>
</div>
2. Menerapkan CSS untuk Perilaku Sticky dan Jenis Penampung
Tetapkan container-type: scroll-state pada elemen position: sticky, bukan pada ancestor yang dapat di-scroll. Selalu tentukan container-name, karena kueri ini mungkin bertingkat dan penampung tanpa nama akan bertabrakan. Anda juga dapat menggabungkannya dengan kueri ukuran, misalnya, container-type: scroll-state inline-size.
.sticky-container {
position: sticky;
top: 0;
/* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
.sticky-header {
/* Base styles for the header */
background: #f0f0f0;
padding: 20px;
transition: background-color 0.3s, box-shadow 0.3s;
}
Penting: Status scroll dikueri oleh turunan penampung status scroll. Gaya dalam kueri @container tidak akan berlaku untuk .sticky-container itu sendiri, tetapi untuk turunannya (seperti .sticky-header). Anda tidak dapat menata gaya elemen penampung itu sendiri dengan kueri status scroll-nya sendiri.
3. Menargetkan Status Terhenti
Gunakan kueri @container scroll-state(...) untuk menerapkan gaya saat header macet.
/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
.sticky-header {
background: #0056b3;
color: white;
box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
}
}
Tips: Anda juga dapat menggunakan properti logis seperti stuck: inset-block-start atau stuck: inset-inline-start untuk mendukung internasionalisasi (i18n) dan tata letak kanan ke kiri (RTL) dengan lebih baik dengan mengkueri tepi yang relatif terhadap alur, bukan tepi fisik.
Catatan tentang Perubahan Dimensi
Jika penautan scroll tidak dinonaktifkan, mengubah properti yang memengaruhi tata letak (tinggi, padding, ukuran font) saat elemen melekat dapat menyebabkan kedipan visual. Penambatan scroll pada konten dalam alur di bawah elemen melekat menyesuaikan offset scroll untuk mengimbangi perubahan tata letak, yang mendorong elemen kembali keluar dari posisi melekatnya dan memicu osilasi.
Nonaktifkan penambatan scroll pada induk elemen melekat untuk menghindari hal ini:
.section {
overflow-anchor: none;
}
Terapkan overflow-anchor: none ke induk langsung elemen position: sticky, bukan ke :root atau body, yang akan menonaktifkan penambatan scroll untuk seluruh halaman. Dengan demikian, Anda dapat dengan bebas mengubah properti apa pun dalam status stuck, termasuk properti model kotak dan transformasi.
JANGAN: Mengandalkan overflow-anchor: none untuk elemen yang menempel di bagian bawah. Penautan scroll hanya mengompensasi pergeseran tata letak di atas posisi scroll saat ini, sehingga tidak berpengaruh pada elemen yang menempel di bawah. Jangan mengubah properti model kotak dalam status melekat untuk header yang melekat di bawah.
Strategi penggantian
Kueri status scroll adalah progressive enhancement. Di browser yang tidak mendukung container-type: scroll-state, header akan tetap berada di bagian atas (karena position: sticky), tetapi tidak akan berubah secara visual. Untuk sebagian besar kasus penggunaan, ini adalah pendekatan yang direkomendasikan.
Jika posisi melekat tidak terlalu penting, tetapi position: sticky tanpa gaya yang berbeda akan memberikan pengalaman yang lebih buruk daripada tidak ada posisi melekat sama sekali, Anda juga dapat menggabungkan position: sticky dan deklarasi terkait dalam kueri @supports:
.sticky-container {
@supports (container-type: scroll-state) {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
}
Tips: Jika gaya "stuck" Anda memerlukan warna latar belakang yang berbeda agar mudah dibaca, pertimbangkan untuk menyiapkan tata letak Anda agar gaya default berfungsi di mana saja (Progressive Enhancement). Jika harus menggunakan penggantian, Anda dapat membatasi perilaku position: sticky itu sendiri di dalam kueri @supports (container-type: scroll-state).
Jika transformasi visual sangat penting untuk desain (misalnya, status melekat memperkenalkan latar belakang atau pemadatan yang tanpanya konten tidak dapat dibaca), Anda dapat menerapkan penggantian JavaScript yang andal menggunakan IntersectionObserver. Anda harus menduplikasi gaya CSS di class .is-stuck. Polyfill generik berikut memeriksa getComputedStyle untuk melintasi ke atas dan menemukan penampung scroll yang benar (secara default ke area pandang), yang cocok dengan perilaku @container scroll-state(stuck: top):
function getScrollParent(node) {
if (node == null || node === document.body || node === document.documentElement) {
return null; // default to viewport
}
if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
const overflow = getComputedStyle(node).overflow;
if (overflow !== 'visible' && overflow !== 'clip') {
return node;
}
}
return getScrollParent(node.parentNode);
}
document.querySelectorAll('.sticky-container').forEach(container => {
const root = getScrollParent(container);
const topOffset = parseFloat(getComputedStyle(container).top) || 0;
const observer = new IntersectionObserver(
([e]) => {
// Toggle the fallback class on the sticky header container
e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
},
{
root: root,
threshold: [1],
rootMargin: `-${topOffset + 1}px 0px 0px 0px`
}
);
observer.observe(container);
});
Catatan: Pola IntersectionObserver generik ini juga dapat digunakan sebagai polyfill untuk kueri scroll-state(scrollable).