7.3
Komponen Gambar dan Skeleton
Direktori src/components/ menyediakan dua komponen yang menangani gambar yang belum selesai dimuat. NextImage membungkus next/image dan melacak status pemuatan lewat satu nilai state, sedangkan Skeleton menyediakan kotak placeholder dengan shimmer berbasis animasi CSS. Keduanya dipakai berdampingan di halaman pratinjau komponen (src/app/components/page.tsx:410-4301); lihat Katalog Komponen Pratinjau untuk susunan halaman itu.
Status pemuatan di NextImage
NextImage menyimpan satu potongan state bernama status. Nilai awalnya bergantung pada prop useSkeleton: 'loading' bila useSkeleton bernilai true, dan 'complete' bila tidak. Inisialisasi itu hanya berjalan sekali, ketika komponen di-mount (src/components/NextImage.tsx:24-372).
stateDiagram-v2
[*] --> loading : useSkeleton = true
[*] --> complete : useSkeleton = false
loading --> complete : onLoadingComplete
complete --> [*]
style loading fill:#f59e0b,color:#000
style complete fill:#2d7d46,color:#fff
%% fig: Siklus hidup status pada NextImage
%% desc: Status dimulai sebagai loading hanya bila useSkeleton aktif, lalu berpindah ke complete saat callback onLoadingComplete dijalankan.Perpindahan dari loading ke complete dipicu oleh prop onLoadingComplete milik Image yang memanggil setStatus('complete') (src/components/NextImage.tsx:49-563). Di berkas ini tidak ada useEffect maupun pemanggilan setStatus('loading') lain, sehingga status tidak kembali ke loading ketika src berganti setelah gambar pertama selesai dimuat.
Selama status bernilai 'loading', className untuk Image dirakit dari classNames?.image yang digabung dengan cn, lalu ditambah animate-pulse beserta classNames?.blur (src/components/NextImage.tsx:44-484). Penggabungan kelas itu memakai util cn yang membungkus clsx dan twMerge (src/lib/utils.ts:5-75); rincian util tersebut ada di Utilitas, Logger, dan Environment.
Elemen terluar komponen ini adalah figure, bukan Image. figure menerima className dari pemanggil, dan hanya memasang style lebar bila className tidak memuat w- (src/components/NextImage.tsx:39-436). Pemeriksaan itu dilakukan dengan className?.includes('w-') (src/components/NextImage.tsx:24-372).
⚠️
includes('w-')adalah pencocokan substring, bukan pencocokan kelas utuh. Kelas apa pun yang memuat teksw-membuat komponen menganggap lebar sudah ditetapkan, sehinggafiguretidak lagi mendapatstyleselebar${width}px.
⚠️ JSDoc komponen mengingatkan agar
useSkeletontidak dipakai pada gambar transparan, karena lataranimate-pulseakan terlihat di belakang gambar (src/components/NextImage.tsx:19-237).
Props NextImage
Tipe NextImageProps menggabungkan tiga hal: prop tambahan milik NextImage, satu union yang mewajibkan width dan height kecuali ketika layout: 'fill' dipakai, dan seluruh ImageProps dari next/image (src/components/NextImage.tsx:6-178).
| Prop | Tipe | Default | Perilaku |
|---|---|---|---|
useSkeleton | boolean | false | Menentukan nilai awal status; true berarti mulai dari 'loading' |
classNames.image | string | – | Kelas dasar untuk Image, selalu ikut digabung lewat cn |
classNames.blur | string | – | Kelas tambahan yang hanya aktif selama status 'loading' |
alt | string | wajib | Diteruskan langsung ke Image |
width, height | string | number | wajib kecuali layout: 'fill' | Dipakai figure untuk lebar fallback ${width}px |
layout | 'fill' | – | Membuat width dan height menjadi opsional |
className | string | – | Ditaruh di figure; kehadiran w- mematikan lebar fallback |
...rest | ImageProps | – | Diteruskan ke Image setelah prop di atas |
Skeleton dan animasi shimmer
Skeleton adalah div tipis yang meneruskan seluruh React.ComponentPropsWithoutRef<'div'>. Kelas dasarnya selalu animate-shimmer bg-[#f6f7f8], lalu digabung dengan className dari pemanggil lewat cn (src/components/Skeleton.tsx:5-209).
Karena tidak ada kelas latar gradien di className dasarnya, efek shimmer dibangun dari dua sumber: utilitas animate-shimmer dan style inline (src/components/Skeleton.tsx:5-209).
| Bagian | Nilai |
|---|---|
| Kelas dasar | animate-shimmer bg-[#f6f7f8] |
backgroundImage | linear-gradient(to right, #f6f7f8 0%, #edeef1 20%, #f6f7f8 40%, #f6f7f8 100%) |
backgroundSize | 700px 100% |
backgroundRepeat | no-repeat |
| Token animasi | --animate-shimmer: shimmer 1.3s linear infinite |
@keyframes shimmer | background-position bergerak dari -700px 0 ke 700px 0 |
Nilai token dan keyframes itu didefinisikan di src/styles/globals.css dalam blok @theme (src/styles/globals.css:24-2710) dan @keyframes shimmer (src/styles/globals.css:55-6211). Lebar backgroundSize sama dengan jarak tempuh keyframes, yaitu 700px, sehingga gradien bergeser tepat satu lebar kotak per siklus. Sistem token warna dan tema selengkapnya dibahas di Styling, Token Warna, dan Tema.
graph LR
kf["@keyframes shimmer: -700px 0 to 700px 0"] --> theme["--animate-shimmer: shimmer 1.3s linear infinite"]
theme --> skel["Skeleton div with animate-shimmer"]
skel --> inline["inline style: linear-gradient, backgroundSize 700px 100%"]
caller["className from caller: h-72 w-72"] --> skel
status["status === 'loading'"] --> pulse["animate-pulse and classNames.blur"]
pulse --> img["Image from next/image"]
style kf fill:#f59e0b,color:#000
style theme fill:#f59e0b,color:#000
style skel fill:#8b5cf6,color:#fff
style inline fill:#8b5cf6,color:#fff
style caller fill:#8b5cf6,color:#fff
style status fill:#f59e0b,color:#000
style pulse fill:#f59e0b,color:#000
style img fill:#336791,color:#fff
%% fig: Sumber kelas animasi pada Skeleton dan NextImage
%% desc: Skeleton mengambil animasi dari token --animate-shimmer yang merujuk keyframes shimmer, sedangkan NextImage memakai animate-pulse selama status masih loading.Perhatikan bahwa keduanya memakai mekanisme berbeda: Skeleton bergantung pada token --animate-shimmer di globals.css, sementara NextImage memakai utilitas animate-pulse untuk efek denyut (src/components/NextImage.tsx:44-484).
Pemakaian di halaman pratinjau
Halaman src/app/components/page.tsx menampilkan keduanya di dalam satu daftar. NextImage dipanggil dengan useSkeleton, className='w-32 md:w-40', src favicon, serta width dan height bernilai string '180'. Karena className memuat w-, figure tidak mendapat lebar inline, dan lebar diatur oleh kelas responsif tersebut. Skeleton dipanggil dengan className='h-72 w-72' sehingga ukurannya ditentukan pemanggil (src/app/components/page.tsx:410-4301).
⚠️
widthyang dikirim sebagai string pada contoh itu tetap dipakai untuk atributwidthmilikImage, bukan untukstylefigure. BilaclassNametidak memuatw-, nilai itulah yang menjadistylelebar fallback (src/components/NextImage.tsx:39-436).
Untuk komponen tombol dan tautan yang juga tampil di halaman pratinjau itu, lihat Komponen Tombol dan Komponen Link.