depth-tilt
v0.2.2
Published
Layered, spring-powered 3D tilt for the web with zero required CSS.
Maintainers
Readme
depth-tilt
Lightweight 3D tilt, layered depth, spring motion, and front/back card flipping for vanilla JavaScript.
- No stylesheet import
- No dependencies
- Works with HTML data attributes
- TypeScript types included
Install
pnpm add depth-tiltnpm install depth-tiltyarn add depth-tiltCDN
<script type="module">
import DepthTilt from "https://cdn.jsdelivr.net/npm/depth-tilt/dist/index.js";
const tilt = new DepthTilt();
</script>Quick start
<article class="card" data-depth-tilt>
<img src="cover.jpg" alt="" data-depth="-20" />
<h2 data-depth="35">Depth Tilt</h2>
<button data-depth="55">Explore</button>
</article>import DepthTilt from "depth-tilt";
const card = new DepthTilt();Without a target, DepthTilt initializes every [data-depth-tilt] element.
Your CSS only defines the card's size and appearance:
.card {
width: 320px;
min-height: 220px;
border-radius: 24px;
background: #17182c;
}Select cards
Pass a selector to initialize every matching card:
const card = new DepthTilt(".product-card");Or pass an element:
const element = document.querySelector(".product-card");
const card = new DepthTilt(element);You can inspect the matched elements through elements:
const cards = new DepthTilt("[data-depth-tilt]");
console.log(cards.elements);Layer depth
Add data-depth to any descendant:
<article class="card" data-depth-tilt>
<div class="card__background" data-depth="-24"></div>
<p data-depth="18">Category</p>
<h2 data-depth="38">Layered interface</h2>
<a href="/details" data-depth="56">Open project</a>
</article>- Positive values move toward the viewer.
- Negative values move away from the viewer.
- Larger differences create stronger depth.
Call refresh() after adding layers or changing data-depth values:
card.refresh();Front/back flip
Create two sides and choose click or hover:
<article class="card" data-depth-tilt data-depth-tilt-flip="click">
<div class="card__face card__front" data-depth-tilt-front>
<h2 data-depth="30">Front</h2>
</div>
<div class="card__face card__back" data-depth-tilt-back>
<h2 data-depth="30">Back</h2>
</div>
</article>.card {
width: 320px;
aspect-ratio: 1.6;
border-radius: 20px;
}
.card__face {
padding: 24px;
border: 1px solid #ddd;
border-radius: inherit;
background: white;
box-shadow: 0 20px 50px #0002;
}
.card__back {
color: white;
background: #17182c;
}Put the background, border, radius, and shadow on the front and back elements. DepthTilt handles the flip styles and animation.
Click
<article data-depth-tilt data-depth-tilt-flip="click">Click, Enter, and Space toggle the card.
Hover
<article data-depth-tilt data-depth-tilt-flip="hover">The card returns to the front when the pointer leaves.
Flip speed
flipDuration is measured in milliseconds:
const card = new DepthTilt(".card", {
flipDuration: 800,
});The default is 600.
Manual control
The default flip trigger is manual:
const card = new DepthTilt(".card");
card.flip();
card.unflip();
card.toggleFlip();
console.log(card.flipped);Start on the back
<article
data-depth-tilt
data-depth-tilt-flip="click"
data-depth-tilt-flipped
>Ignore part of a clickable card
Links, buttons, and form controls do not toggle a click-flip card. For any other element,
add data-depth-tilt-no-flip:
<div data-depth-tilt-no-flip>Do not flip from here</div>Spring physics
Spring motion is enabled by default:
const card = new DepthTilt(".card", {
spring: {
stiffness: 160,
damping: 9,
},
});Disable spring motion:
const card = new DepthTilt(".card", {
spring: false,
});Data attributes
| Attribute | Purpose |
| --- | --- |
| data-depth-tilt | Marks a tilt card |
| data-depth="30" | Sets a descendant's Z-depth |
| data-depth-tilt-flip="click" | Flips on click, Enter, or Space |
| data-depth-tilt-flip="hover" | Flips while hovered |
| data-depth-tilt-front | Marks the front side |
| data-depth-tilt-back | Marks the back side |
| data-depth-tilt-flipped | Starts with the back visible |
| data-depth-tilt-no-flip | Prevents click flip from a descendant |
| data-depth-tilt-max="10" | Overrides the maximum angle |
| data-depth-tilt-perspective="1200" | Overrides perspective |
Options
interface DepthTiltOptions {
maxTilt?: number; // 14
perspective?: number; // 900
scale?: number; // 1.025
lift?: number; // 0
axis?: "both" | "x" | "y"; // "both"
reverse?: boolean; // false
flipTrigger?: "manual" | "hover" | "click"; // "manual"
flipDuration?: number; // 600
spring?: boolean | { // enabled
stiffness?: number; // 160
damping?: number; // 9
mass?: number; // 1
precision?: number; // 0.001
};
disableOnTouch?: boolean; // true
respectReducedMotion?: boolean; // true
reducedMotionBehavior?: "disable" | "instant"; // "disable"
resetOnLeave?: boolean; // true
depthSelector?: string; // "[data-depth]"
className?: string; // "depth-tilt"
autoStart?: boolean; // true
}Methods and state
card.start();
card.stop();
card.destroy();
card.reset();
card.reset(true); // reset immediately
card.refresh();
card.update({ maxTilt: 18 });
card.flip();
card.unflip();
card.toggleFlip();card.element;
card.active;
card.disabled;
card.destroyed;
card.flipped;stop() can be followed by start(). destroy() permanently removes the instance.
Axis and direction
new DepthTilt(".horizontal-card", {
axis: "y",
});
new DepthTilt(".reversed-card", {
reverse: true,
});bothenables both axes.xenables onlyrotateX.yenables onlyrotateY.
Touch and reduced motion
Touch tilt is ignored by default:
new DepthTilt(".card", {
disableOnTouch: false,
});When prefers-reduced-motion: reduce is active, DepthTilt disables motion by default.
Use instant pointer response instead:
new DepthTilt(".card", {
reducedMotionBehavior: "instant",
});CSS custom properties
DepthTilt updates these properties on the card:
--depth-tilt-rotate-x
--depth-tilt-rotate-y
--depth-tilt-pointer-x
--depth-tilt-pointer-y
--depth-tilt-progress-x
--depth-tilt-progress-y
--depth-tilt-scale
--depth-tilt-liftEach depth layer also receives:
--depth-tilt-depthBrowser support
DepthTilt is a browser-only library for modern browsers with Pointer Events and CSS 3D transforms.
