3D Tilt Card
A card leans towards the pointer like a real object in your hand, with a soft glare that follows it, then springs back when the pointer leaves.
Move your pointer over me
How to use it
- Paste the snippet just before
</head>. - Add
data-tilt-cardto any card, image or panel.
Options
Add any of these attributes to the same element.
data-max | Largest tilt, in degrees. Default 12. |
data-scale | How much it grows while hovered. Default 1.03. |
data-glare="false" | Turn off the moving glare. |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// 3D Tilt Card: a card leans towards the pointer like a physical object, with
// a soft glare that follows it, then springs back when the pointer leaves.
// Usage: <div class="card" data-tilt-card>…</div>
// Optional: data-max (degrees, default 12), data-scale (hover scale, default
// 1.03), data-glare="false" to turn the glare off.
var DEFAULTS = { max: 12, scale: 1.03 };
var css =
'.tc-glare{position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;' +
'background:radial-gradient(circle at var(--tc-x,50%) var(--tc-y,50%),rgba(255,255,255,.55),rgba(255,255,255,0) 60%);mix-blend-mode:soft-light}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function setup(el) {
var d = el.dataset;
var max = +d.max || DEFAULTS.max;
var scale = +d.scale || DEFAULTS.scale;
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
var glare = null;
if (d.glare !== 'false') {
glare = document.createElement('div');
glare.className = 'tc-glare';
el.appendChild(glare);
}
anime.utils.set(el, { perspective: '900px' });
el.addEventListener('pointermove', function (e) {
var r = el.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width; // 0 … 1
var py = (e.clientY - r.top) / r.height;
anime.animate(el, {
rotateY: (px - 0.5) * 2 * max,
rotateX: (0.5 - py) * 2 * max,
scale: scale,
duration: 500,
ease: 'out(3)'
});
if (glare) {
glare.style.setProperty('--tc-x', px * 100 + '%');
glare.style.setProperty('--tc-y', py * 100 + '%');
anime.animate(glare, { opacity: 1, duration: 300 });
}
});
el.addEventListener('pointerleave', function () {
anime.animate(el, { rotateX: 0, rotateY: 0, scale: 1, duration: 1200, ease: 'outElastic(1, .5)' });
if (glare) anime.animate(glare, { opacity: 0, duration: 400 });
});
}
function init() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
document.querySelectorAll('[data-tilt-card]').forEach(function (el) {
if (el._tc) return;
el._tc = true;
setup(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div class="card" data-tilt-card>
<img src="product.jpg" alt="">
<h3>Product</h3>
</div>