D
P
0
← Semua artikel Read in English

Layout, Overflow & Cascade CSS

Ganti Berkas Gambar Tanpa Sentuh CSS Tapi Tampilnya Kira-Kira Dua Kali Lebih Besar? `max-width` Mengunci Kanvas, Bukan Subjeknya

· · 5 menit baca
Ganti Berkas Gambar Tanpa Sentuh CSS Tapi Tampilnya Kira-Kira Dua Kali Lebih Besar? `max-width` Mengunci Kanvas, Bukan Subjeknya

Klien mengirim tiga PNG desain kemasan versi baru. Saya konversi ke WebP dengan cwebp -lossless -z 9 -metadata none, hasilnya 178 sampai 187KB per berkas, lalu saya timpa berkas lama memakai nama yang sama persis. Tidak ada satu baris kode pun yang perlu disentuh. WebP lama saya cadangkan dulu ke folder cadangan terpisah dengan akhiran -prev di nama berkasnya, supaya versi sebelumnya masih bisa dikembalikan kalau perlu.

Klien membuka halamannya dan balasannya cuma dua kata: terlalu besar.

Bukan soal warna, bukan soal crop. Cuma ukurannya, dan selisihnya kira-kira dua kali lipat. Angka itu perkiraan mata, bukan hasil pengukuran piksel.

Yang tidak berubah itu justru alat ukurnya

Aturan yang membatasi gambar itu tidak disentuh sama sekali di antara aset lama dan aset baru:

max-width: 320px;
height: auto;

Refleks biasanya mengejar bagian yang berubah, dan di sini yang berubah cuma isi berkasnya. Justru karena kotak pembatasnya konstan di 320px, dia jadi satu-satunya hal di TKP yang bisa saya percaya sebagai garis dasar.

Dari situ logikanya jadi memaksa. Kalau pembatasnya tetap 320px sementara yang dilihat klien jadi kira-kira dua kali lebih besar, maka yang menentukan besar-kecil di mata orang memang bukan pembatas itu. Yang berubah adalah hubungan antara subjek dan kanvas yang menampungnya, dan hubungan itu tidak tinggal di CSS. Dia tinggal di dalam berkas gambarnya.

Membaca dimensinya lebih dulu

Sebelum menebak apa pun tentang isi gambar, angkanya bisa dibaca dulu. ffprobe cukup untuk ini dan keluarannya terbaca sekilas:

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height -of csv=s=x:p=0 lama.webp

Berkas lama berukuran 2400x2400, dan subjeknya cuma menempati sekitar 50% lebar kanvas karena ada padding di sekelilingnya. Berkas baru berukuran 1195x1600, hasil potongan rapat, dan subjeknya memenuhi frame.

Akar masalahnya: CSS mengunci kanvas, bukan subjeknya

max-width: 320px bekerja pada elemen gambarnya, artinya pada seluruh kanvas berkas itu, bukan pada benda yang tergambar di dalamnya. Browser tidak tahu mana subjek dan mana ruang kosong. Baginya keduanya sama-sama piksel yang harus diskalakan bersama sampai lebar elemennya 320px.

Jadi hitungannya begini. Di berkas lama, kanvas 2400 piksel diskalakan sampai muat di 320px, dan karena subjeknya cuma menempati sekitar 50% lebar kanvas, yang benar-benar terlihat cuma sekitar separuh dari 320px itu. Sisanya ruang kosong yang ikut memakan jatah lebar. Di berkas baru, kanvas 1195 piksel juga diskalakan sampai 320px, tapi kali ini subjeknya memenuhi kanvasnya, jadi yang terlihat mendekati 320px penuh.

Angka CSS-nya identik. Ukuran tampilnya kira-kira dua kali. Sebagai bonus, karena height: auto, tinggi elemennya ikut rasio berkasnya, jadi perpindahan dari kanvas persegi ke kanvas portrait juga menggeser tinggi bloknya padahal stylesheet-nya diam.

Aturan umumnya bisa ditulis begini. Waktu menukar aset gambar secara drop-in dengan nama berkas yang sama, kalau dimensi kanvasnya berbeda dari yang lama, atau rasio subjek terhadap kanvasnya yang berbeda, ukuran tampilnya berubah walaupun CSS sama sekali tidak disentuh. Yang benar-benar aman cuma kalau dimensi kanvasnya sama dan proporsi subjek terhadap kanvasnya juga sama, bukan salah satunya saja.

Kenapa perbaikannya tidak dari CSS

Jalan tercepat jelas menyetel ulang max-width sampai kelihatan pas. Catatan yang saya tinggalkan dari kasus ini justru menolak jalan itu: menambal dari sisi CSS lebih berisiko karena bisa merusak keselarasan gambar itu dengan elemen saudaranya di susunan flex atau grid.

Angka CSS-nya juga bukan cuma satu. Lebar gambar itu responsif per breakpoint, yaitu 70vw, 50vw, dan 36vw, jadi kompensasinya bukan satu nilai yang diketik sekali.

Keputusannya mendahulukan perbaikan di sisi aset ketimbang di sisi CSS, supaya kesinambungan visualnya tetap satu banding satu.

Perbaikannya: kembalikan kanvasnya ke ukuran lama

Yang perlu dilakukan cuma memberi berkas baru itu ruang kosong yang sama seperti punya berkas lama, dengan subjeknya di tengah. Filter pad di ffmpeg mengerjakan ini tanpa menyentuh piksel subjeknya, dia cuma menempelkan bidang tambahan di sekeliling gambar aslinya.

Padding-nya dikerjakan di PNG kiriman klien, bukan langsung di WebP, lalu hasilnya dikonversi ulang:

ffmpeg -i baru.png \
  -vf "pad=2400:2400:(ow-iw)/2:(oh-ih)/2:color=0x00000000" \
  -y padded.png

Angka 2400:2400 bukan angka bulat yang saya karang, itu dimensi berkas lama yang tadi dibaca ffprobe. Ekspresi (ow-iw)/2 dan (oh-ih)/2 menaruh gambar asli tepat di tengah kanvas barunya, dan color=0x00000000 adalah hitam dengan alpha nol, jadi bidang tambahannya benar-benar transparan.

Setelah itu baru dikodekan ke WebP lagi, dengan flag yang sama seperti konversi pertama:

cwebp -lossless -z 9 -metadata none padded.png -o baru.webp

Urutan kerjanya memang tiga langkah itu: ffprobe untuk membaca dimensi, ffmpeg pad untuk menyamakan kanvas, lalu cwebp.

Hasilnya sekitar 190KB per berkas, dimensinya cocok dengan aset lama, dan ukuran tampilnya kembali seperti semula.

Gejala yang sama pernah muncul terbalik

Sebulan sebelumnya, logo di proyek yang sama memberi contoh kebalikannya. Logo lama berukuran 1304x1310, persegi, sebagian besar isinya padding putih, dan teksnya duduk di bagian bawah tengah. Untuk menampilkannya butuh akal-akalan: sebuah viewport overflow: hidden, gambar yang diperbesar sampai tingginya 280, lalu digeser dengan transform: translate.

Logo baru datang dengan kanvas 2400x913 dan padding yang minim, teksnya mengisi sekitar 85% lebar dan 50% tinggi. Karena rasio subjek terhadap kanvasnya berubah drastis, akal-akalan tadi malah bisa dibuang, dan logonya dirender apa adanya dengan height: 36px; width: auto; max-width: none.

Hukumnya sama persis, cuma arahnya terbalik. Kanvas yang berpadding memaksa trik di CSS, kanvas yang rapat membuat triknya tidak perlu lagi.

Aturan yang sama dipakai lagi untuk video

Belakangan aturan ini dipakai lagi waktu ada aset video versi baru. Apa adanya, subjeknya menempati 30,8% lebar frame sementara aset lama yang sudah tayang 24,9%, jadi kalau dipasang begitu saja dia akan tampil 24% lebih besar. Ukurannya disamakan dengan aset lama lewat scale=988:1560 di dalam kanvas 1440x1920, sehingga rasionya jatuh tepat di 24,9%.

Pelajaran yang saya catat dari situ: bandingkan aset baru dengan aset yang sudah diterima, bukan dengan baseline internal berkas barunya sendiri.

Yang saya simpan dari sini