D
P
0
← Semua artikel Read in English

Shopify, Liquid & Theme CLI

Header Overlay Transparan Menutupi Heading di Halaman Non-Homepage? Satu Perilaku Header Tidak Bisa Melayani Dua Jenis Halaman

· · 7 menit baca
Header Overlay Transparan Menutupi Heading di Halaman Non-Homepage? Satu Perilaku Header Tidak Bisa Melayani Dua Jenis Halaman

Header tema ini awalnya dirancang sebagai overlay transparan yang fixed, khusus untuk hero homepage. Bukan header serbaguna yang kebetulan dipakai di homepage, tapi benar-benar dibuat untuk satu halaman itu. Dia position: fixed, jadi tidak ikut alur dokumen, dan hero mulai dari puncak viewport dengan header menumpang di atasnya.

.site-header {
  position: fixed;
  top: 32px; /* di bawah announcement bar */
  background: transparent;
}

Selama pekerjaan masih berputar di homepage, semuanya terlihat rapi. Latarnya transparan, hero besar di belakangnya menanggung kontras, dan begitu scroll melewati hero header berubah solid dengan efek glass-blur, plus auto-hide yang menyembunyikannya saat scroll turun.

Lalu klien menandai sesuatu di halaman lain. Di halaman FAQ dan di halaman produk, header itu mengambang di atas dan menutupi heading halaman beserta kontennya. Klien menandainya dua kali. Catatan pola yang saya simpan menuliskan gejala yang persis sama dan menyebut heading FAQ secara spesifik.

Kenapa ini terjadi

Penyebabnya tidak rumit dan tidak ada hubungannya dengan CSS yang salah tulis. Hanya varian header untuk homepage yang dibangun. Sumbernya menyatakan itu dengan lugas: halaman tanpa hero setinggi viewport butuh header sticky yang solid dengan konten didorong ke bawahnya, dan satu perilaku header tidak bisa melayani dua-duanya. Catatan pola saya menuliskan konsekuensinya dalam satu kalimat, kalau kamu cuma membangun varian homepage, konten halaman lain terpotong di belakang header yang mengambang.

Mekanismenya sendiri adalah perilaku CSS yang biasa. position: fixed mengeluarkan elemen dari alur dokumen, jadi tidak ada ruang yang disisakan untuknya. Konten pertama di dokumen tetap mulai dari titik nol, dan header duduk di depannya. Di homepage itu justru yang diinginkan, karena yang tertimpa cuma bagian atas hero yang memang dibiarkan lapang. Di halaman yang isi pertamanya adalah judul, yang tertimpa adalah judul itu.

Polanya sendiri lazim di tema premium. Homepage punya hero yang memenuhi viewport, sehingga headernya masuk akal sebagai overlay transparan yang berubah solid setelah lewat hero. Halaman lain, produk, FAQ, halaman konten, tidak punya hero sama sekali. Mereka butuh sticky solid sejak baris pertama, dengan konten duduk di bawah header, bukan di baliknya.

Perbaikannya

Perbaikannya dua varian yang digerbangi template.name == 'index' di Liquid. Homepage tetap overlay transparan fixed dengan scroll-solid dan auto-hide, halaman lain dapat kelas varian statis. Gerbangnya diselesaikan sekali di markup, dengan variabel yang diset dari nama template, lalu kelas dan atributnya cuma ditempel di halaman non-homepage.

{%- assign is_home = false -%}
{%- if template.name == 'index' -%}{%- assign is_home = true -%}{%- endif -%}
<header class="site-header{% unless is_home %} site-header--static{% endunless %}" {% unless is_home %}data-static-nav{% endunless %}>

Varian statisnya sticky di top: 36px dengan z-index: 98, selalu solid, tanpa state transparan, tanpa auto-hide. Auto-hide dimatikan dengan menetralkan transform di state tersembunyi, jadi state-nya boleh saja tetap diset tapi tidak menghasilkan pergeseran apa pun. Konten didorong ke bawah lewat padding-top pada body yang dipicu selektor :has().

.site-header--static {
  position: sticky;
  top: 36px;
  z-index: 98;
}
 
.site-header--static[data-header-hidden] {
  transform: none;
}
 
body:has(.site-header--static) {
  padding-top: 36px;
}

Sisi JavaScript-nya membaca atribut yang sama dan melewati pemasangan scroll handler kalau atribut itu ada.

const isStatic = header.hasAttribute('data-static-nav');
if (!isStatic) {
  /* pasang scroll handler untuk state solid dan auto-hide */
}

Jadi di halaman yang tidak membutuhkannya, tidak ada listener yang menghitung arah scroll sama sekali. Bukan listener yang dipasang lalu diminta diam, tapi listener yang memang tidak pernah dipasang.

Kenapa sticky, kenapa padding di body, dan kenapa :has()

Alasan memilih position: sticky dan bukan relative tertulis di catatan saya. Dengan static atau relative, header ikut hilang tergulung bersama konten, sementara yang diinginkan adalah header tetap terlihat di atas saat konten digulung. Sticky memakunya di bawah announcement bar.

Alasan padding-top-nya ditaruh di body, bukan di container konten, juga soal announcement bar. Bar itu position: fixed; top: 0 dan memakan 36px, jadi konten harus mulai di bawahnya. Dengan header yang sticky, padding-top di body mencapai itu secara alami tanpa perlu menghitung apa pun di JavaScript.

Alasan memakai :has() adalah supaya induk bisa bereaksi terhadap varian anaknya tanpa JS. Body tidak perlu tahu template apa yang sedang dirender, dia cuma perlu tahu bahwa di dalamnya ada header varian statis. Catatan saya menyebut dukungan peramban untuk selektor itu di atas 92 persen.

Angka offset yang tidak sepakat antar catatan

Di berkas catatan pola yang sama, varian statis ditulis top: 36px dengan komentar bahwa 36px itu menyamai tinggi announcement bar, sementara varian homepage ditulis top: 32px. Dua angka untuk offset yang seharusnya berasal dari benda yang sama, selisih 4px. Catatan projeknya sendiri memihak angka yang lain, di sana announcement bar dicatat setinggi 32px dan header duduk di top: 32px.

Saya tidak akan berpura-pura tahu mana yang benar. Yang bisa saya baca dari situ cuma satu, angka tinggi announcement bar ditulis di lebih dari satu tempat, dan tempat-tempat itu boleh berbeda tanpa ada yang mengeluh.

Perbedaan kecil yang sejenis juga muncul di kata, bukan di angka. Spec projeknya menulis pihak yang melapor sebagai Client, catatan polanya menulis User, untuk laporan yang isinya identik.

Empat piksel yang menutupi header itu sendiri

Belakangan, di kejadian yang terpisah dari ini, headernya justru yang tertutup oleh announcement bar. Diagnosisnya datang dari tangkapan layar DevTools yang dikirim klien.

Di berkas section header ada dua blok @media (max-width: 767px) yang duplikat. Yang pertama, di baris 122, menyetel top: 32px. Yang kedua, di baris 237, menyetel top: 28px. Karena urutan cascade, yang menang adalah yang belakangan, jadi 28px. Sementara itu padding konten ditulis calc(32px + 56px), yang berangkat dari asumsi announcement 32px sama dengan offset header 32px.

Hitungannya bisa diikuti sampai ke gejala yang terlihat. Announcement bar setinggi 32px, header duduk di 28px, jadi 32 dikurang 28 menyisakan 4px bagian atas header yang tertutup bar. Header itu sendiri punya padding: 8px 14px, dan begitu 4px teratasnya tertutup, padding atas yang benar-benar terlihat tinggal 8 dikurang 4, yaitu 4px, sementara padding bawahnya tetap 8px. Yang klien lihat bukan angka-angka itu, tapi logo yang jaraknya ke atas lebih rapat daripada ke bawah.

Perbaikan ini akhirnya dicabut seluruhnya

Varian statisnya sempat dilaporkan bekerja. Sticky di bawah announcement, konten terdorong ke bawah, di halaman konten, halaman produk, dan halaman koleksi.

Lalu permintaannya berubah, beberapa kali, dan catatan projek merekam urutannya. Kondisi awal adalah homepage overlay transparan dan halaman lain sticky-static. Permintaan berikutnya menyatukan semua halaman ke satu varian, position: sticky; top: 36px dengan body padding-top: 36px. Setelah itu dorongan konten ke bawah justru ditolak, karena yang diinginkan header menyambung langsung dengan announcement bar, jadi header dipindah ke position: fixed; top: 36px tanpa mendorong body sama sekali. Keadaan akhirnya, header selalu fixed dan solid dengan teks dan logo gelap, tanpa auto-hide.

Yang dibuang bersamaan dengan itu adalah seluruh mekanisme di atas: kelas varian statis, atribut penandanya, listener scroll untuk transisi state, dan padding-top :has() di body. Penggantinya kelas di tag body dan padding yang dihitung langsung.

<body{% unless template.name == 'index' %} class="page--inner"{% endunless %}>
 
<style>
  body.page--inner { padding-top: calc(36px + 80px); }
</style>

Angkanya adalah tinggi announcement ditambah tinggi header, dan berbeda per breakpoint. Yang di atas menghasilkan 36 ditambah 80 sama dengan 116px. Dua breakpoint lainnya tercatat sebagai 36 ditambah 72, yaitu 108px, untuk mobile, dan 32 ditambah 56, yaitu 88px, untuk tablet. Homepage tetap overlay, headernya fixed di top: 36px tanpa push.

Spec-nya sendiri sudah mencatat akhir cerita ini apa adanya. Iterasi klien berikutnya menyatukan semuanya jadi selalu fixed dan solid, dan yang tersisa sebagai pola yang dapat dipakai ulang tinggal gerbang per-templatenya.

Pelajaran

Saran penerapan ulang di catatan saya cuma satu kalimat, dan saya masih setuju dengannya. Di tema premium mana pun yang homepage-nya digerakkan hero, rancang dua varian header sejak awal, hero-overlay untuk homepage dan sticky-solid untuk semua halaman lain, lalu tukar lewat deteksi template Liquid.

Yang bertahan dari pekerjaan ini memang bukan CSS-nya. Nilai top, padding-top, dan z-index semuanya sudah dicabut dan diganti oleh iterasi berikutnya. Yang bertahan adalah gerbangnya, pertanyaan template.name == 'index' yang dijawab sekali di markup, lalu dipakai CSS dan JavaScript tanpa satu pun dari keduanya perlu tahu halaman apa yang sedang dirender.

Dan satu hal kecil yang saya bawa dari deretan angka yang tidak sepakat di atas. Offset header dan padding konten itu satu angka yang sama, ditulis di dua tempat atau lebih. Selama tempat-tempat itu boleh berbeda tanpa ada yang menegur, selisihnya cepat atau lambat akan muncul sebagai piksel yang menutupi sesuatu.