Survolez la carte pour l'incliner
3D Tilt Effect
Rotation 3D temps réel sur deux axes depuis la position du curseur — le contenu flotte en avant et une ombre indigo apparaît au survol.
Cet effet est gratuit — la catégorie Cartes en contient 41 au total, dont 6 gratuits. Effect.Labs regroupe 811 effets vanilla. Découvrir la catégorie →
Exemples d'usage
Nouvelle version
Survolez la carte pour l'incliner
Survolez la carte pour l'incliner
Comment ça marche
À chaque mousemove, le script calcule la position du curseur relative au centre de la carte via getBoundingClientRect(). Deux angles sont dérivés : rotateX = (y − centreY) / 10 (inclinaison avant/arrière) et rotateY = (centreX − x) / 10 (inclinaison gauche/droite). Le diviseur 10 plafonne l'inclinaison à environ ±9° en bord de carte.
La transformation est appliquée en une seule chaîne inline : perspective(1000px) rotateX(Xdeg) rotateY(Ydeg). Utiliser perspective() dans la fonction transform plutôt que comme propriété CSS parente lie le point de fuite à l'élément lui-même — chaque carte dans une grille a sa propre perspective, sans interférence.
La propriété CSS transform-style: preserve-3d sur .card-tilt propage le contexte 3D aux enfants. L'élément .tilt-content reçoit translateZ(30px), ce qui le fait flotter visuellement à 30 px devant la surface de la carte — un effet de parallaxe discret entre le fond de la carte et son contenu.
Au mouseleave, le transform revient à perspective(1000px) rotateX(0) rotateY(0) grâce à la transition: transform 0.1s définie en CSS — le rappel est fluide en 100 ms. L'ombre portée indigo du sélecteur .card-tilt:hover suit le même mécanisme CSS natif sans JS supplémentaire.
Accessibilité
- prefers-reduced-motion absent du code : l'inclinaison se déclenche toujours au survol, sans tenir compte de la préférence système. Ajoutez
@media (prefers-reduced-motion: reduce) { .card-tilt { transition: none; } }et désactivez les listeners JS si nécessaire. - L'effet est exclusivement à la souris (
mousemove/mouseleave) — aucune interaction clavier ni touch. La carte reste accessible et lisible sans interaction. - Aucun attribut ARIA n'est ajouté par l'effet. Si la carte est interactive (lien, bouton), ajoutez
role,aria-labelouaria-labelledbymanuellement. - Le contraste du contenu textuel n'est pas affecté par l'inclinaison : couleurs et luminosité restent constantes quelle que soit la rotation.
- L'effet n'intercepte aucun événement clavier et ne piège pas le focus — les éléments enfants interactifs (liens, boutons) restent focusables normalement.
Compatibilité navigateur
Nécessite CSS transform-style: preserve-3d et getBoundingClientRect — disponibles dans tous les navigateurs modernes. Zéro dépendance externe.
Sans support des transformations 3D CSS, la carte s'affiche à plat — aucune erreur JS, le contenu reste lisible et accessible.
Le code
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="card-demo card-tilt" id="tiltCard">
<span class="tilt-content">3D Tilt</span>
</div>
.card-demo {
width: 280px;
height: 180px;
background: var(--bg-card);
border-radius: var(--radius-xl);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
position: relative;
cursor: pointer;
}
.card-tilt {
transform-style: preserve-3d;
transition: transform 0.1s;
}
.card-tilt:hover {
box-shadow: rgba(99, 102, 241, 0.3) 0px 25px 50px;
}
.card-tilt .tilt-content {
transform: translateZ(30px);
}
/* Variables du design system du site, dont ce code depend. Sans elles,
l'acheteur obtenait des coins carres et des couleurs par defaut (revue Owen).
Valeurs reprises de css/style.css — a adapter librement. */
.card-demo, .card-tilt {
--bg-card: #12121a;
--radius-xl: 1rem;
}
/* Le fond de carte est desormais sombre : sans couleur de texte declaree,
le libelle heritait du noir de la page hote et devenait illisible (revue Owen). */
.card-demo { color: #fff; }
(function(){
// 3D Tilt Effect
const tiltCard = document.getElementById('tiltCard');
if (tiltCard) {
tiltCard.addEventListener('mousemove', (e) => {
const rect = tiltCard.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 10;
const rotateY = (centerX - x) / 10;
tiltCard.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
tiltCard.addEventListener('mouseleave', () => {
tiltCard.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
});
}
// Spotlight Effect
const spotlightCard = document.getElementById('spotlightCard');
if (spotlightCard) {
spotlightCard.addEventListener('mousemove', (e) => {
const rect = spotlightCard.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
spotlightCard.style.setProperty('--spotlight-x', x + 'px');
spotlightCard.style.setProperty('--spotlight-y', y + 'px');
});
}
// Card Morph Expand Effect
const morphExpandCard = document.getElementById('morphExpandCard');
if (morphExpandCard) {
morphExpandCard.addEventListener('click', () => {
morphExpandCard.classList.toggle('expanded');
});
// Close when clicking outside
document.addEventListener('click', (e) => {
if (!morphExpandCard.contains(e.target) && morphExpandCard.classList.contains('expanded')) {
morphExpandCard.classList.remove('expanded');
}
});
}
})();
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| Diviseur /10 (JS — lignes rotateX / rotateY) | 10 | Amplitude maximale du tilt. Augmentez pour adoucir (15 → ~6°), diminuez pour accentuer (6 → ~15°). |
| perspective(1000px) (JS — chaîne transform) | 1000px | Distance de la caméra virtuelle. Plus élevé = perspective plus plate ; plus bas = déformation 3D plus prononcée. |
| transition: transform 0.1s (CSS — .card-tilt) | 0.1s | Vitesse de suivi du curseur. 0.05s = snap vif, 0.25s = suivi lent et langoureux. |
| translateZ(30px) (CSS — .card-tilt .tilt-content) | 30px | Profondeur de flottement du contenu devant la surface de la carte. Augmentez jusqu'à 60px pour un effet holographique. |
| box-shadow (CSS — .card-tilt:hover) | rgba(99,102,241,0.3) 0px 25px 50px | Couleur et intensité de l'ombre portée au survol. Remplacez la couleur par votre couleur primaire brand. |
| --bg-card (variable CSS) | définie globalement | Couleur ou dégradé de fond de la carte — redéfinissez la variable sur la carte ou son parent pour personnaliser. |
| width / height (CSS — .card-demo) | 280px / 180px | Dimensions de la carte. Le calcul de rotation utilise getBoundingClientRect — redimensionner ne casse pas l'effet. |
FAQ
L'effet fonctionne-t-il sur mobile (touch) ?
Non — le code écoute mousemove et mouseleave, qui ne se déclenchent pas au toucher. Sur mobile, la carte s'affiche sans inclinaison. Pour ajouter le support touch, écoutez touchmove et calculez la rotation depuis e.touches[0].clientX/Y de la même façon.
Comment appliquer le tilt à plusieurs cartes sur la même page ?
Le code fourni cible un unique élément par son id. Pour plusieurs instances, sélectionnez par classe : document.querySelectorAll('.card-tilt'), puis attachez mousemove et mouseleave à chaque élément dans une boucle — chaque carte calcule ses propres coordonnées via getBoundingClientRect().
Les éléments enfants (boutons, liens) restent-ils cliquables à l'intérieur d'une carte inclinée ?
Oui. La transformation 3D est appliquée via element.style.transform — elle n'intercepte ni les clics ni les événements DOM des enfants. Un bouton ou un lien à l'intérieur de la carte reste pleinement interactif et focusable au clavier.