La création d'interfaces immersives en 3D repose aujourd'hui sur l'utilisation conjointe des transformations CSS3 avancées et de la manipulation du DOM via JavaScript. Cet article détaille la mise en œuvre d'un album rotatif tridimensionnel intégrant un arrière-plan dynamiuqe simulant une pluie de météores et un effet de pluie sur canevas.
Structure Fondamentale du Document HTML
L'architecture repose sur un conteneur principal configuré avec une perspective forcée pour simuler la profondeur spatiale. Les éléments de l'album sont disposés à l'intérieur d'un pivot central.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Galerie 3D Interactive</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Pivot de l'album photo -->
<div id="scene-3d">
<div id="album-carousel">
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<div class="photo-item"></div>
<p class="ground-shadow"></p>
</div>
</div>
<!-- Arrière-plan météores -->
<div class="meteor-container">
<!-- Génération de 50 éléments météores via CSS -->
<div class="meteor-streak"></div>
<!-- ... répéter les streaks ... -->
</div>
<script src="https://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
<script src="app.js"></script>
</body>
</html>
Stylisation CSS et Effets Visuels
Le rendu 3D est obtenu grâce à la propriété transform-style: preserve-3d. Chaque panneau de l'album utilise le reflet via -webkit-box-reflect pour augmenter le réalisme.
:root {
--meteor-color: #64b5f6;
--bg-dark: #050505;
}
body {
margin: 0;
overflow: hidden;
background: radial-gradient(circle at center, #1a1a2e 0%, var(--bg-dark) 100%);
perspective: 1200px;
}
#album-carousel {
position: relative;
width: 140px;
height: 220px;
margin: 25vh auto;
transform-style: preserve-3d;
transform: rotateX(-12deg);
}
.photo-item {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.1);
-webkit-box-reflect: below 12px linear-gradient(transparent, rgba(0,0,0,0.4));
}
/* Animation des météores */
.meteor-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: rotate(-35deg);
}
.meteor-streak {
position: absolute;
background: linear-gradient(90deg, var(--meteor-color), transparent);
height: 2px;
filter: drop-shadow(0 0 5px var(--meteor-color));
animation: slide-down 8s linear infinite;
}
@keyframes slide-down {
0% { transform: translateX(100vw); opacity: 1; }
100% { transform: translateX(-100vw); opacity: 0; }
}
Logique Interactive et Physique de Rotation
Le script suivant gère l'initialisation de la disposition circulaire et l'inertie lors de la rotation manuelle à la souris.
window.onload = function() {
const gallery = document.getElementById('album-carousel');
const items = gallery.getElementsByClassName('photo-item');
const totalItems = items.length;
const angleStep = 360 / totalItems;
// Placement initial
for (let i = 0; i < totalItems; i++) {
items[i].style.backgroundImage = `url('img/${i + 1}.jpg')`;
items[i].style.backgroundSize = 'cover';
items[i].style.transform = `rotateY(${i * angleStep}deg) translateZ(400px)`;
items[i].style.transition = `transform 1s ${ (totalItems - i) * 0.1 }s`;
}
let rotX = -12, rotY = 0;
let lastMouseX, lastMouseY, diffX, diffY;
let autoRotationTimer;
document.onmousedown = function(e) {
clearInterval(autoRotationTimer);
lastMouseX = e.clientX;
lastMouseY = e.clientY;
this.onmousemove = function(ev) {
diffX = ev.clientX - lastMouseX;
diffY = ev.clientY - lastMouseY;
rotY += diffX * 0.15;
rotX -= diffY * 0.15;
gallery.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;
lastMouseX = ev.clientX;
lastMouseY = ev.clientY;
};
this.onmouseup = function() {
this.onmousemove = null;
// Effet d'inertie
autoRotationTimer = setInterval(() => {
diffX *= 0.96;
diffY *= 0.96;
rotY += diffX * 0.15;
rotX -= diffY * 0.15;
gallery.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;
if (Math.abs(diffX) < 0.1 && Math.abs(diffY) < 0.1) {
clearInterval(autoRotationTimer);
}
}, 16);
};
return false;
};
// Gestion du zoom (Perspective)
window.onwheel = function(e) {
let currentPerspective = parseInt(getComputedStyle(document.body).perspective);
let newPerspective = currentPerspective + (e.deltaY > 0 ? 50 : -50);
newPerspective = Math.max(500, Math.min(2000, newPerspective));
document.body.style.perspective = `${newPerspective}px`;
};
};
Simulation de Pluie sur Canevas (API 2D)
Pour ajouter une couche atmosphérique, un moteur de particules léger est implémenté via l'élément <canvas>, gérant la vélocité et les rebonds au sol.
(function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
let width, height;
const drops = [];
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
class Drop {
constructor() { this.init(); }
init() {
this.x = Math.random() * width;
this.y = -20;
this.vy = Math.random() * 5 + 5;
this.size = 1.5;
}
draw() {
ctx.fillStyle = 'rgba(174, 194, 224, 0.6)';
ctx.fillRect(this.x, this.y, this.size, this.size * 3);
}
update() {
this.y += this.vy;
if (this.y > height) this.init();
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
if (drops.length < 300) drops.push(new Drop());
drops.forEach(d => { d.update(); d.draw(); });
requestAnimationFrame(animate);
}
window.addEventListener('resize', resize);
resize();
animate();
})();