JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style>
body {
margin: 0;
padding: 0;
transform-style: preserve-3d;
transform: perspective(800px);
background-color: #eee;
}
</style>
<link rel="stylesheet" href="test.css">
</head>
<body>
<div id="card-limiter"></div>
<template id="card-template">
<div class="x-slider">
<div class="y-slider">
<div class="z-slider">
<div class="roller">
<div class="card"></div>
</div>
</div>
</div>
</div>
</template>
</body>
</html>
CSS
.x-slider {
position: absolute;
width: 200px;
height: 200px;
transition-property: all;
pointer-events: none;
}
.y-slider {
position: absolute;
width: 200px;
height: 200px;
transition-property: all;
}
.z-slider {
position: absolute;
width: 200px;
height: 200px;
transition-property: all;
}
.roller {
position: relative;
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
}
.card {
position: relative;
width: 120px;
height: 150px;
background-color: #444;
background-image: url('https://i.imgur.com/9LRlpwv.png');
background-size: cover;
transition: all 0.1s ease;
border-radius: 8px;
border: 5px solid #333;
pointer-events: all;
}
.card:hover {
border-color: #0f0;
}
JavaScript
class Card {
constructor() {
this.queue = [];
this.inProgress = false;
this.createView();
}
createView() {
const x_slider = document.querySelector('#card-template').content.firstElementChild.cloneNode(true);
const y_slider = x_slider.firstElementChild;
const z_slider = y_slider.firstElementChild;
const roller = z_slider.firstElementChild;
const card = roller.firstElementChild;
this.view = {x_slider, y_slider, z_slider, roller, card};
document.body.insertBefore(x_slider, document.querySelector('#card-limiter'));
}
applyTransform({ x, y, z, rx, ry, rz }, ms, easing) {
this.inProgress = true;
const { x_slider, y_slider, z_slider, roller, card } = this.view;
const duration = ms / 1000 + 's';
if (x !== undefined) {
x_slider.style.transform = `translateX(${x}px)`;
x_slider.style.transitionDuration = duration;
x_slider.style.transitionTimingFunction = easing;
}
if (y !== undefined) {
y_slider.style.transform = `translateY(${y}px)`;
y_slider.style.transitionDuration = duration;
y_slider.style.transitionTimingFunction = easing;
}
if (z !== undefined) {
z_slider.style.transform = `translateZ(${z}px)`;
z_slider.style.transitionDuration = duration;
z_slider.style.transitionTimingFunction = easing;
}
if (rx !== undefined || ry !== undefined || rz !== undefined) {
let arr = [];
if (rx !== undefined) {
arr.push(`rotateX(${rx}rad)`);
}
if (ry !== undefined) {
arr.push(`rotateY(${ry}rad)`);
}
if (rz !== undefined) {
arr.push(`rotateZ(${rz}rad)`);
}
roller.style.transform = arr.join(' ');
roller.style.transitionDuration = duration;
...