D
P
0
← Semua artikel Read in English

JavaScript, DOM & Animasi Browser

Bar Hitam 38px Ikut Tertanam di PNG Frame Hero yang Baru? `cropdetect` Mengukurnya, Lalu Crop-nya Dititipkan ke Konversi WebP

· · 5 menit baca
Bar Hitam 38px Ikut Tertanam di PNG Frame Hero yang Baru? `cropdetect` Mengukurnya, Lalu Crop-nya Dititipkan ke Konversi WebP

Bar hitam di atas dan bawah sebuah frame biasanya urusan rasio: kanvasnya 16:9, isinya lebih lebar, sisanya diisi hitam oleh pemutar. Yang datang kali ini beda. Bar-nya sudah jadi piksel di dalam PNG, dan kalau dibiarkan, dia akan ikut digambar ke canvas hero seolah bagian dari gambarnya.

Klien mengirim sekuens PNG walkthrough properti yang baru untuk hero situsnya, 196 frame berukuran 1920×1080, ditaruh di folder sumber assets/seq-src/. Sekuens ini menggantikan sekuens lama yang 291 frame. Waktu saya buka, tiap frame punya bar hitam 38px di atas dan 38px di bawah yang tertanam di pikselnya. Area konten sebenarnya 1920×1004, dan 1920 dibagi 1004 kira-kira 1,91, jadi materinya 1.91:1, bukan 16:9 seperti yang dikatakan dimensi berkasnya.

Mengukur bar-nya, bukan menebaknya

Angka 38 itu bisa saja ditaksir dari layar, tapi ffmpeg punya filter yang memang dibuat untuk ini. cropdetect memindai frame, mencari tepi area yang bukan hitam, lalu melaporkan persegi panjang yang dia temukan dalam bentuk yang bisa langsung ditempel ke filter crop. Untuk frame ini dia mengeluarkan crop=1916:1004:2:38, yang membenarkan bar 38px tadi.

Nilai yang akhirnya saya pakai saat encode sedikit berbeda dari laporan itu:

# cropdetect first: crop=1916:1004:2:38
ffmpeg -i in.png -vf crop=1920:1004:0:38 -c:v libwebp -q:v 85 -preset photo out.webp

cropdetect menawarkan lebar 1916 dengan offset x 2. Hitungannya gampang: 1920 dikurangi offset 2 dikurangi lebar 1916 menyisakan 2 kolom di tepi kanan, jadi laporan itu berarti 2 kolom di kiri dan 2 kolom di kanan ikut dianggap cukup gelap untuk dihitung bar. Yang saya kejar cuma letterbox-nya, jadi dari laporan itu saya ambil tinggi 1004 dan offset y 38 saja, sementara lebar tetap penuh 1920 dengan offset x 0. Sisanya adalah encoder: libwebp dengan -q:v 85 dan -preset photo, dijalankan batch untuk 196 berkas.

Kenapa bar-nya tidak boleh tinggal

Hero ini bukan elemen <video>, tapi canvas yang frame-nya digambar mengikuti ScrollTrigger. Painter-nya memakai pola cover yang umum:

const scale = Math.max(cw / iw, ch / ih);

Untuk canvas, letterbox yang sudah jadi piksel sama saja dengan konten. Painter tidak tahu mana bar dan mana gambar. Kalau PNG 1920×1080 itu dikonversi apa adanya lalu dipasang di viewport 16:9, skalanya tepat 1, dan 38px hitam di atas serta di bawah ikut tampil sebagai bagian hero.

Begitu bar-nya dibuang, painter yang sama tidak perlu disentuh. Dari sumber 1920×1004 ke viewport 16:9 misalnya 1920×1080, cw/iw bernilai 1 dan ch/ih bernilai 1080 dibagi 1004, sekitar 1,076. Math.max memilih 1,076, gambarnya diskalakan ke sekitar 2065×1080, dan kelebihan lebar sekitar 145px dibagi rata ke kiri dan kanan lalu terpotong. Viewport terisi penuh tanpa hitam, dan yang hilang cuma sedikit tepi kiri dan kanan.

Angka sesudah konversi

Berkas assets/seq-webp/frame_NNNN.webp diganti seluruhnya, dari 291 berkas lama menjadi 196 berkas baru. Karena jumlah frame yang dibaca renderer datang dari template beranda, atribut data-total-frames="291" di sana diubah menjadi "196".

Hasil encode-nya 196 frame dengan total 24 MB, dan itu ukuran foldernya yang sebenarnya. Angka rata-rata sekitar 135KB per frame di catatan saya cuma taksiran, dan memang tidak berkalian persis dengan totalnya. Catatan kerja saya menulis baseline-nya sebagai 291 frame kali sekitar 900KB, sekitar 262 MB, dan menyebut penurunannya sekitar 91%. Inventaris awal di wave yang sama mencatat angka lain untuk sekuens lama yang benar-benar dilayani: 291 WebP lossless 1920x1080 seberat 167MB, dikonversi dari 291 PNG master seberat 355MB. Dua baseline itu tidak saya rekonsiliasi di catatan, jadi saya laporkan dua-duanya. Kalau pembandingnya 167MB, 24 dibagi 167 kira-kira 0,14, jadi sekitar 86% lebih kecil. Mana pun yang dipakai, ordonya sama.

Yang lebih terasa di runtime adalah preload-nya. Renderer memuat eager 30 frame pertama dan lookahead 24 frame saat scroll. Dengan sekuens lama yang rata-rata sekitar 575KB per frame, 30 frame pertama itu 30 kali 575KB, kira-kira 17 MB. Dengan sekuens baru, 30 kali 135KB kira-kira 4 MB.

Sekuens lama lossless, dan itu pilihan klien

Perbedaan ukuran per frame di atas bukan cuma soal 38px yang dibuang. Sekuens lama dikodekan sebagai WebP lossless di effort 6, karena klien secara eksplisit memilih lossless daripada lossy, dan rata-ratanya jatuh di sekitar 575KB per frame. Konverternya waktu itu bukan ffmpeg, melainkan skrip berbasis sharp di scripts/frames-to-webp.mjs.

Sebelum sekuens baru datang, sudah ada catatan terbuka untuk memantau metrik muat aset lossless 167MB itu, dengan lossy q90 sekitar 22MB sebagai cadangan. Sekuens baru dikodekan lossy q=85 lewat ffmpeg, dan hasil 24 MB-nya duduk di kisaran yang sama dengan cadangan yang sudah dihitung sebelumnya.

PNG sumbernya jangan ikut naik

Ada satu ekor yang gampang lupa. Setelah konversi, folder assets/seq-src/ yang berisi 196 PNG seberat sekitar 262MB masih ada di tema, padahal runtime hanya membaca assets/seq-webp/ yang 24MB. Folder itu dihapus di tahap pre-deploy pada 2026-05-25, tepat sebelum migrasi produksi. Konversi frame-nya sendiri tercatat di wave sebelumnya tanpa tanggal.

Pelajaran yang saya tulis waktu itu singkat: PNG sumber dan WebP teroptimasi, pilih satu untuk produksi. WebP-nya yang cepat didekode dan ringan di bandwidth, jadi PNG sumber tidak boleh masuk paket deploy. Folder tema yang sebelumnya 542MB, termasuk node_modules, aset brand, PNG sumber, dan video, turun ke payload produksi 28MB.

Yang saya bawa pulang