Cara Membuat Efek Orbit 3D, Kartu Tilt, dan Carousel Silinder dengan CSS & JavaScript Murni (plus CMS PHP)
Cara Membuat Efek Orbit 3D, Kartu Tilt, dan Carousel Silinder dengan CSS & JavaScript Murni (plus CMS PHP)
Portofolio developer sering terlihat seragam: hero dengan foto bulat, deretan kartu proyek, lalu formulir kontak. Padahal hanya dengan CSS 3D dan sedikit JavaScript, halaman yang sama bisa terasa jauh lebih hidup tanpa library animasi yang berat. Di artikel ini kita bedah lima teknik inti dari template Orbit: logo teknologi yang mengorbit foto profil, kartu yang miring mengikuti kursor, carousel testimoni berbentuk silinder, latar bintang parallax, dan satu trik keamanan di sisi PHP agar CMS-nya aman dipakai.
1. Sistem orbit 3D: cincin miring + rotasi balik
Idenya: buat sebuah cincin (lingkaran) lalu miringkan dengan rotateX, sehingga dari depan terlihat seperti elips. Cincin diputar terus dengan rotateZ. Logo diletakkan di tepi cincin, tapi logo harus tetap menghadap kamera. Caranya dengan membatalkan semua rotasi induk di elemen logo.
<div class="ring" style="--rad:270px;--dur:40s">
<div class="sat" style="--a:0deg"><div class="face">...logo...</div></div>
<div class="sat" style="--a:72deg"><div class="face">...logo...</div></div>
</div>
.ring{width:calc(var(--rad)*2);height:calc(var(--rad)*2);border-radius:50%;
transform-style:preserve-3d;animation:spin var(--dur) linear infinite;--tilt:66deg}
@keyframes spin{
from{transform:rotateX(var(--tilt)) rotateZ(0)}
to{transform:rotateX(var(--tilt)) rotateZ(360deg)}}
/* letakkan satelit di tepi cincin */
.sat{position:absolute;left:50%;top:50%;transform-style:preserve-3d;
transform:rotateZ(var(--a)) translateX(var(--rad)) rotateZ(calc(var(--a) * -1))}
/* logo memutar balik dengan durasi yang sama agar selalu tegak */
.face{animation:counter var(--dur) linear infinite}
counter{
{:() (((--tilt) * -))}
{:(-) (((--tilt) * -))}}
Kuncinya ada di urutan transformasi. Total rotasi yang diterima .face adalah rotateX(tilt) · rotateZ(putar) · rotateZ(-putar) · rotateX(-tilt), yang hasilnya nol. Logo ikut berkeliling, tetapi tidak ikut berguling. Agar logo yang lewat di belakang benar-benar tertutup foto, foto dan cincin harus berada di satu konteks 3D yang sama: bungkus keduanya dengan elemen ber-transform-style: preserve-3d, dan beri foto translateZ(0).
Di PHP, daftar logo yang mengorbit diambil dari teknologi yang ditandai unggulan, lalu dibagi rata ke dua cincin:
<?php foreach ([[0, 270, 40], [1, 196, 28]] as [$ri, $rad, $dur]):
$items = array_values(array_filter($orbit, fn($k) => $k % 2 === $ri, ARRAY_FILTER_USE_KEY)); ?>
<div class="ring r<?= $ri ?>" style="--rad:<?= $rad ?>px;--dur:<?= $dur ?>s">
<?php foreach ($items as $i => $t): ?>
<div class="sat" style="--a:<?= round(360 / max(1, count($items)) * $i) ?>deg">...</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
Sudut tiap logo dihitung otomatis, jadi pemilik situs cukup mencentang "unggulan" di CMS.
2. Kartu tilt mengikuti kursor
Efek kartu "miring" dibuat dari posisi kursor relatif terhadap tengah kartu. Nilainya -0,5 sampai 0,5, lalu dikalikan sudut maksimal.
el.addEventListener('pointermove', e => {
const r = el.getBoundingClientRect();
const x = (e.clientX - r.left) / r.width - 0.5;
const y = (e.clientY - r.top) / r.height - 0.5;
el.style.transform = `perspective(900px) rotateY(${x * 10}deg) rotateX(${-y * 10}deg)`;
});
el.addEventListener('pointerleave', () => { el.style.transform = ''; });
Supaya terasa berlapis, bagian teks di dalam kartu diberi transform: translateZ(30px) dan induknya transform-style: preserve-3d. Efek ini hanya dipasang jika matchMedia('(hover: hover)') bernilai benar, karena di HP tidak ada kursor dan efek miring justru mengganggu saat menggulir.
3. Carousel silinder 3D untuk testimoni
Bayangkan kartu-kartu testimoni ditempel di dinding sebuah tabung. Setiap kartu diputar 360/n derajat lalu didorong keluar sejauh jari-jari tabung. Jari-jari dihitung dari lebar kartu agar sisi-sisinya bersentuhan:
const n = items.length, step = 360 / n;
const radius = Math.round((w / 2) / Math.tan(Math.PI / n)) + 40;
items.forEach((q, i) => {
q.style.transform = `rotateY(${i * step}deg) translateZ(${radius}px)`;
});
car.style.transform = `translateZ(${-radius}px) rotateY(${-idx * step}deg)`;
Rumus (w/2) / tan(π/n) adalah jarak dari pusat ke sisi segi-n beraturan. Tabung lalu dimundurkan sejauh radius supaya kartu terdepan tidak terlalu dekat dengan kamera. Untuk berpindah testimoni, cukup ubah idx dan biarkan transition CSS yang menganimasikan. Kartu yang tidak aktif diberi kelas dim agar fokus tetap pada kartu depan, dan geser jari (swipe) dideteksi dari selisih pointerdown dan pointerup lebih dari 40 piksel.
4. Latar bintang parallax yang ringan
Bintang dibuat di server sebagai SVG, bukan ratusan elemen div. Seed acak dibuat tetap agar posisi bintang tidak berubah setiap halaman dimuat:
function bintang(int $n, int $seed, float $rMax): string {
mt_srand($seed);
$o = '';
for ($i = 0; $i < $n; $i++)
$o .= '<circle cx="' . mt_rand(0, 1600) . '" cy="' . mt_rand(0, 1000) . '" r="' . round(mt_rand(5, (int) ($rMax * 10)) / 10, 1) . '"/>';
mt_srand();
return $o;
}
Ada tiga lapis SVG dengan ukuran bintang berbeda. Saat scroll, setiap lapis digeser dengan kecepatan berbeda. Pekerjaan berat dihindari dengan requestAnimationFrame dan listener passive:
let ticking = false;
addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
layers.forEach((l, i) => { l.style.transform = `translate3d(0, ${-scrollY * (0.04 + i * 0.07)}px, 0)`; });
ticking = false;
});
}, { passive: true });
translate3d membuat browser memindahkan lapisan di GPU sehingga tetap mulus di HP. Semua animasi dimatikan jika pengguna mengaktifkan prefers-reduced-motion.
5. Sesi CMS otomatis batal saat password diganti
Efek visual tidak ada artinya kalau CMS mudah dibobol. Satu masalah klasik: admin mengganti password karena curiga akunnya dipakai orang lain, tetapi sesi lama di perangkat lain tetap aktif. Solusinya, sesi menyimpan sidik jari dari hash password, bukan sekadar ID pengguna:
function sidik_password(string $hash): string {
return hash_hmac('sha256', $hash, session_id() . '|porto');
}
function user(): ?array {
$row = q('SELECT id, nama, username, password FROM users WHERE id = ?', [$_SESSION['uid']])->fetch();
if ($row && hash_equals((string) ($_SESSION['sidik'] ?? ''), sidik_password($row['password']))) return $row;
unset($_SESSION['uid'], $_SESSION['sidik']);
return null;
}
Setiap request mencocokkan sidik di sesi dengan hash password terbaru di database. Begitu password diganti, hash berubah dan semua sesi lama otomatis gagal dicocokkan. Sesi yang melakukan penggantian cukup memanggil masuk() lagi agar sidiknya diperbarui. Perbandingan memakai hash_equals supaya aman dari serangan timing.
Ringkasan
- Orbit 3D = cincin
rotateXyang berputarrotateZ, ditambah rotasi balik di logo agar selalu tegak. - Tilt kartu cukup memetakan posisi kursor ke
rotateX/rotateY, hanya untuk perangkat ber-hover. - Carousel silinder memakai rumus jari-jari segi-n
(w/2)/tan(π/n). - Parallax ringan: SVG dari server,
requestAnimationFrame,translate3d, dan hormatiprefers-reduced-motion. - Sidik HMAC dari hash password membuat sesi di perangkat lain batal saat password diganti.
Download source code lengkapnya
Template Orbit lengkap dengan CMS (profil, proyek + galeri, 70+ logo teknologi, pengalaman, layanan, testimoni, pesan masuk, dashboard pengunjung), data contoh, dan ilustrasi siap pakai bisa kamu unduh gratis.
Download Source Code Template Portofolio Orbit + CMS di Papicode
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.
Tutorial lainnya
Cara Membuat Sistem Tiket Travel dengan Pilih Kursi di Google Apps Script: Layout Armada, Anti Kursi Dobel, dan Kursi Kedaluwarsa
20 Oktober 2026
Cara Membuat Website Donasi Transparan dengan Google Apps Script: Kode Unik Transfer dan Laporan Penyaluran
20 Oktober 2026