Kamera Virtual & Interpolasi Lerp
Teknik menggerakkan sudut pandang gambar latar secara mulus di setiap frame menggunakan rumus interpolasi linear (lerp) tanpa memicu render ulang React.
Membaca sekitar 3 menit
Teknik menggerakkan sudut pandang gambar latar secara mulus di setiap frame menggunakan rumus interpolasi linear (lerp) tanpa memicu render ulang React.
Komponen PhotoWorld membaca preset koordinat tiap adegan dari tabel CAM lalu memperbarui transform CSS dan filter optik (brightness, saturate, blur) langsung ke DOM lewat requestAnimationFrame.
Mengubah satu foto statis menjadi pengalaman ruang berdimensi lengkap dengan efek fokus lensa dan bilah letterbox layaknya adegan film.
Masalahnya dulu
Kebanyakan undangan pernikahan digital dibuat seperti brosur panjang: tamu menggulir halaman dari atas ke bawah melewati deretan kotak teks yang kaku. Saya ingin pendekatan yang berbeda—sebuah pengalaman sinematik di mana layar terkunci penuh seperti layar bioskop, dan setiap guliran menggerakkan kamera mendekat ke mempelai pria, bergeser ke mempelai wanita, lalu mundur perlahan sambil mengaburkan latar belakang ketika kartu acara muncul.
Bagaimana kamera virtual ini bekerja
Alih-alih memuat delapan gambar latar yang berbeda atau menggunakan engine WebGL berat yang menguras baterai ponsel, saya menggunakan satu lanskap foto resolusi tinggi di dalam komponen PhotoWorld. Setiap adegan memiliki preset kamera di dalam konstanta CAM yang menyimpan koordinat geser (pan X & Y), perbesaran (scale), kecerahan (brightness), saturasi, tingkat keburaman (blur), hingga tinggi bilah hitam sinematik (letterbox bar) di atas dan bawah layar.
Jangan gunakan state React untuk animasi 60 frame per detik; biarkan React menentukan adegan tujuannya, lalu serahkan pergerakan kameranya ke loop requestAnimationFrame dan mutasi gaya DOM langsung.
Implementasi di ngodingan ini
Di dalam PhotoWorld, perubahan prop scene hanya memperbarui referensi target (tgt.current). Sebuah loop requestAnimationFrame berjalan terus-menerus mengejar nilai target tersebut menggunakan fungsi interpolasi linear (lerp) dengan faktor kecepatan 0.052:
const CAM = [
{ x: 0, y: 0, s: 1.0, br: 0.7, sat: 0.8, bl: 0, bar: 0 }, // 0 Hero
{ x: -8, y: 6, s: 1.58, br: 0.8, sat: 0.9, bl: 0, bar: 65 }, // 1 Groom
{ x: 8, y: 6, s: 1.58, br: 0.8, sat: 0.9, bl: 0, bar: 65 }, // 2 Bride
{ x: 0, y: 3, s: 1.18, br: 0.65, sat: 0.76, bl: 0, bar: 75 }, // 3 Quote
{ x: 2, y: 4, s: 1.22, br: 0.52, sat: 0.72, bl: 6, bar: 85 }, // 4 Story
{ x: 0, y: -6, s: 1.4, br: 0.46, sat: 0.62, bl: 5, bar: 90 }, // 5 Event
];
useEffect(() => {
let raf = 0;
const loop = () => {
const c = cam.current, t = tgt.current, sp = 0.052;
c.x = lerp(c.x, t.x, sp);
c.y = lerp(c.y, t.y, sp);
c.s = lerp(c.s, t.s, sp);
c.br = lerp(c.br, t.br, sp);
c.sat = lerp(c.sat, t.sat, sp);
c.bl = lerp(c.bl, t.bl, sp);
if (worldRef.current)
worldRef.current.style.transform = `translate(${c.x}%,${c.y}%) scale(${c.s})`;
if (imgRef.current) {
const bl = c.bl > 0.12 ? ` blur(${c.bl.toFixed(2)}px)` : "";
imgRef.current.style.filter = `brightness(${c.br.toFixed(3)}) saturate(${c.sat.toFixed(3)}) sepia(.09)${bl}`;
}
raf = requestAnimationFrame(loop);
};
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);Jebakan dan detail yang harus dijaga
- Filter CSS blur(0px) tetap memaksa GPU membuat layer komposit ekstra di beberapa browser mobile. Karena itu string blur hanya dipasang jika nilainya di atas ambang batas 0.12px.