Ringkasan
“Wavy infinite carousel” menggabungkan React Three Fiber (R3F) untuk render 3D/WebGL dengan GLSL untuk mendistorsi kartu/gambar secara dinamis sehingga terasa hidup saat bergerak tak-berujung. Inti tekniknya: instancing untuk performa, vertex shader untuk gelombang, fragment shader untuk pewarnaan/tekstur, dan loop wrap untuk ilusi tak-berujung.
Konsep Dasar
- Carousel tak-berujung: Deretan panel bergerak saling menyambung; saat panel melewati batas, posisinya “di-teleport” ke sisi lain.
- Wavy (gelombang): Posisi vertex dimodifikasi sinus/cosinus menggunakan
uTime,uAmplitude, danuFrequency. - Instancing: Satu mesh direplikasi ribuan kali secara efisien; atribut kustom seperti
aOffsetmemberi variasi fase gelombang. - Interaksi: Pointer/drag menambah modulasi (misalnya kecepatan atau amplitudo sesaat).
Struktur Arsitektur Mini
- Assets: Siapkan tekstur (atlas untuk irit drawcall) dan aktifkan mipmaps/anisotropy.
- R3F Canvas: Render scene;
useFrameuntuk animasi posisi dan waktu shader. - InstancedMesh: Susun barisan panel;
setMatrixAt+InstancedBufferAttributeuntuk offset/fase. - ShaderMaterial: Vertex shader = gelombang; fragment shader = sampling tekstur + efek ringan.
- Loop: Logika wrap di CPU (JS) atau di shader (modulus).
Contoh Shader (Ilustratif)
Shader berikut bersifat ilustratif (bukan salinan artikel sumber). Ubah sesuai kebutuhan produksi.
Vertex Shader (gelombang)
uniform float uTime;
uniform float uAmplitude; // tinggi gelombang
uniform float uFrequency; // kerapatan gelombang
uniform float uSpeed; // kecepatan fase
attribute float aOffset; // variasi per-instance
varying vec2 vUv;
void main() {
vec3 p = position;
float phase = (aOffset + p.x) * uFrequency + uTime * uSpeed;
p.y += sin(phase) * uAmplitude;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
}
Fragment Shader (sampling & toning)
uniform sampler2D uMap;
uniform float uTint; // 0.0..1.0 untuk efek toning ringan
varying vec2 vUv;
void main() {
vec4 tex = texture2D(uMap, vUv);
vec3 tone = mix(tex.rgb, tex.rgb * vec3(0.95, 1.05, 1.10), uTint);
gl_FragColor = vec4(tone, tex.a);
}
Contoh R3F (Ilustratif)
Contoh singkat: membuat baris panel instanced yang bergeser dan di-wrap. Gunakan loader tekstur sesuai kebutuhan (atlas/array).
// Carousel.tsx (ilustratif)
import * as THREE from "three";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { useMemo, useRef } from "react";
function WavyCarousel({ count = 20, spacing = 1.2 }) {
const meshRef = useRef<THREE.InstancedMesh>(null);
const matRef = useRef<THREE.ShaderMaterial>(null);
const { clock } = useThree();
// Posisi awal per panel dan offset fase
const { matrices, offsets } = useMemo(() => {
const dummy = new THREE.Object3D();
const m = new Array(count);
const o = new Float32Array(count);
for (let i = 0; i < count; i++) {
dummy.position.set(i * spacing, 0, 0);
dummy.rotation.set(0, 0, 0);
dummy.updateMatrix();
m[i] = dummy.matrix.clone();
o[i] = i * 0.37; // fase unik agar gelombang tidak seragam
}
return { matrices: m, offsets: o };
}, [count, spacing]);
// Buat atribut offset untuk instancing
const offsetAttr = useMemo(() => new THREE.InstancedBufferAttribute(offsets, 1), [offsets]);
// Parameter loop
const totalWidth = (count - 1) * spacing;
const speed = 0.6; // unit per detik
useFrame((state, dt) => {
const t = clock.getElapsedTime();
if (matRef.current) {
matRef.current.uniforms.uTime.value = t;
}
// Geser semua panel ke kiri, wrap saat melewati batas
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.matrix.copy(matrices[i]);
dummy.position.set(
((dummy.position.x - speed * dt) % (totalWidth + spacing) + (totalWidth + spacing)) % (totalWidth + spacing),
0,
0
);
dummy.updateMatrix();
meshRef.current!.setMatrixAt(i, dummy.matrix);
}
meshRef.current!.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh ref={meshRef} args={[undefined as any, undefined as any, count]}>
<planeGeometry args={[1, 0.6, 16, 16]}>
<instancedBufferAttribute attach='attributes-aOffset' args={[offsetAttr.array, 1]} />
</planeGeometry>
<shaderMaterial
ref={matRef}
uniforms={{
uTime: { value: 0 },
uAmplitude: { value: 0.08 },
uFrequency: { value: 2.5 },
uSpeed: { value: 2.0 },
uTint: { value: 0.15 },
uMap: { value: new THREE.TextureLoader().load('/path/to/texture.jpg') }
}}
vertexShader={`uniform float uTime;uniform float uAmplitude;uniform float uFrequency;uniform float uSpeed;attribute float aOffset;varying vec2 vUv;void main(){vec3 p=position;float phase=(aOffset+p.x)*uFrequency+uTime*uSpeed;p.y+=sin(phase)*uAmplitude;vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}`}
fragmentShader={`uniform sampler2D uMap;uniform float uTint;varying vec2 vUv;void main(){vec4 tex=texture2D(uMap,vUv);vec3 tone=mix(tex.rgb,tex.rgb*vec3(0.95,1.05,1.10),uTint);gl_FragColor=vec4(tone,tex.a);}`}
transparent
/>
</instancedMesh>
);
}
export default function Scene() {
return (
<Canvas camera={{ position: [0, 0, 3], fov: 50 }}>
<ambientLight intensity={0.6} />
<WavyCarousel count={24} spacing={1.1} />
</Canvas>
);
}
Kontrol Interaksi
- Hover/drag: Saat drag, tambah kecepatan atau ubah
uAmplitudesementara lalu lerp kembali. - Autoplay: Gunakan kecepatan dasar + easing agar transisi halus saat interaksi berakhir.
- Pointer easing: Mapping posisi pointer ke parameter shader (misal frekuensi lokal).
Performa & Kualitas
- Instancing: Minimalkan draw calls. Pakai
InstancedMeshalih-alih banyak mesh terpisah. - Tekstur: Gunakan atlas atau texture array untuk beberapa gambar.
- Mipmaps/anisotropy: Kurangi shimmer saat carousel bergerak cepat.
- Ukuran geometry: Segmen secukupnya (mis. 16–32) untuk wave halus tanpa berat.
- Mobile fallback: Turunkan jumlah instance, amplitude, atau matikan efek berat.
Aksesibilitas & UX
- Reduced motion: Hormati
prefers-reduced-motiondengan menurunkanuAmplitude/speed. - Keyboard: Sediakan kontrol alternatif (prev/next) di luar kanvas.
- SEO/Fallback: Tampilkan daftar gambar statis bila WebGL tidak tersedia.
<img src="data:image/svg+xml;utf8,Kontrol Shader• uAmplitude (tinggi gelombang)• uFrequency (kerapatan)• uSpeed (kecepatan fase)Guardrails Performa• Instancing • atlas • mipmaps• Batasi segmen geometry• Fallback mobileUX & A11y• Respek prefers-reduced-motion • Kontrol keyboard• SEO fallback (daftar gambar statis)” alt=”Panel parameter shader, guardrails performa, dan rekomendasi UX/A11y” />
Checklist Implementasi Cepat
- Siapkan aset tekstur (optimasi ukuran, aktifkan mipmaps/anisotropy).
- Buat
InstancedMeshpanel (plane) + atributaOffset. - Terapkan vertex/fragment shader (uniform:
uTime,uAmplitude,uFrequency,uSpeed). - Tambahkan loop wrap di
useFramedan kontrol interaksi (drag/hover). - Uji performa (desktop/mobile) dan sediakan fallback.
Sumber & Kredit
- Artikel inspirasi: Codrops
- React Three Fiber: docs.pmnd.rs
- GLSL dasar: The Book of Shaders
Terbit: 2 Desember 2025 • Sumber inspirasi: Codrops
Catatan gambar: Kedua ilustrasi dibuat khusus untuk artikel ini dan dirilis sebagai CC0 (domain publik) — bebas digunakan tanpa atribusi. Jika situs Anda memblokir data URI, beri tahu saya agar saya kirimkan file PNG/SVG untuk diunggah ke Media Library.
Catatan: Teks dan kode merupakan ringkasan serta pengembangan orisinal, bukan salinan langsung dari sumber.