Hitung Mundur & Primitif Reveal Viewport
Perhitungan waktu mundur detik demi detik menuju hari pernikahan serta komponen pembungkus animasi kemunculan elemen.
Membaca sekitar 2 menit
Perhitungan waktu mundur detik demi detik menuju hari pernikahan serta komponen pembungkus animasi kemunculan elemen.
Menghitung selisih waktu menuju 7 Juni 2026 setiap 1000ms dengan Day.js dan membungkus teks cerita ke dalam komponen RevealUp serta SlideIn berbasis useInView.
Menyetel properti once: false dengan margin negatif pada useInView membuat undangan tetap terasa hidup saat digulir naik-turun berulang kali.
Masalahnya dulu
Di hampir setiap seksi undangan terdapat blok teks cerita, nama orang tua, hingga detail lokasi acara yang perlu muncul secara anggun ketika memasuki layar. Jika logika useInView ditulis berulang-ulang di setiap paragraf, kode komponen utama akan penuh sesak dan sulit dirawat. Di saat bersamaan, Seksi 6 membutuhkan penghitung waktu mundur waktu nyata menuju tanggal pernikahan 7 Juni 2026 pukul 10:00.
Abstraksi komponen RevealUp, SlideIn, dan timer Day.js
Untuk menjaga kode tetap deklaratif, pola animasi kemunculan diekstrak menjadi dua komponen primitif: RevealUp (muncul dari bawah sejauh 50px) dan SlideIn (meluncur dari kiri atau kanan sejauh 80px). Keduanya menggunakan kurva cubic-bezier yang sama ([0.22, 1, 0.36, 1]) dan memantau viewport lewat useInView dengan margin "-10% 0px -10% 0px". Sementara itu, selisih waktu menuju hari pernikahan dihitung menggunakan weddingDate.diff(dayjs()) setiap detik dan dipecah menjadi satuan hari, jam, menit, dan detik.
Komponen animasi primitif yang seragam menjaga ritme visual di seluruh halaman tetap konsisten tanpa mengulang konfigurasi transisi.
Implementasi di ngodingan ini
Berikut adalah definisi komponen RevealUp dan logika hitung mundur berbasis Day.js di src/pages/HomePage.tsx:
const weddingDate = dayjs("2026-06-07 10:00:00");
const RevealUp = ({ children, delay = 0, className = "" }) => {
const ref = useRef(null);
const isInView = useInView(ref, { once: false, margin: "-10% 0px -10% 0px" });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
transition={{ duration: 0.9, delay, ease: [0.22, 1, 0.36, 1] }}
className={className}
>
{children}
</motion.div>
);
};
useEffect(() => {
const interval = setInterval(() => {
const diff = weddingDate.diff(dayjs());
if (diff <= 0) return;
setTimeLeft({
hari: Math.floor(diff / 86400000),
jam: Math.floor((diff / 3600000) % 24),
menit: Math.floor((diff / 60000) % 60),
detik: Math.floor((diff / 1000) % 60),
});
}, 1000);
return () => clearInterval(interval);
}, []);Jebakan dan detail yang harus dijaga
- Menyetel once: false pada useInView sengaja dipilih agar saat tamu menggulir kembali ke seksi atas untuk membaca ulang cerita, animasi tetap dimainkan dengan halus.
- Pengecekan if (diff <= 0) return mencegah angka hitung mundur berubah menjadi negatif setelah hari pernikahan tiba.
- Margin viewport "-10% 0px -10% 0px" memastikan animasi baru terpicu ketika elemen sudah benar-benar masuk sedikit ke dalam layar, bukan saat masih berada di tepi paling bawah yang belum terlihat mata.