<section class="demo">
<div class="demo__scene">
<div class="demo__wheel" id="wheel">
<article data-tone="a">Aurora</article>
<article data-tone="b">Basalt</article>
<article data-tone="c">Cobalt</article>
<article data-tone="d">Dune</article>
<article data-tone="e">Ember</article>
</div>
</div>
<p>Drag to orbit</p>
</section>.demo {
min-height: 100%;
height: 100%;
display: grid;
place-items: center;
background: #08080a;
color: #f4f4f2;
font-family: "Helvetica Neue", system-ui, sans-serif;
}
.demo__scene {
perspective: 1100px;
width: 100%;
height: 320px;
transform-style: preserve-3d;
}
.demo__wheel {
position: relative;
width: 180px;
height: 230px;
margin: 40px auto 0;
transform-style: preserve-3d;
cursor: grab;
}
.demo__wheel:active { cursor: grabbing; }
.demo__wheel article {
position: absolute;
inset: 0;
display: grid;
place-items: end start;
padding: 16px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
backface-visibility: hidden;
font-size: 15px;
}
.demo__wheel article[data-tone="a"] { background: linear-gradient(160deg, #2b2f4a, #14161f); }
.demo__wheel article[data-tone="b"] { background: linear-gradient(160deg, #23262a, #101113); }
.demo__wheel article[data-tone="c"] { background: linear-gradient(160deg, #1b3350, #0c1420); }
.demo__wheel article[data-tone="d"] { background: linear-gradient(160deg, #3a3123, #17130d); }
.demo__wheel article[data-tone="e"] { background: linear-gradient(160deg, #4a2422, #1d0f0e); }
.demo p {
margin: 8px 0 0;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: #5a5a62;
}const wheel = document.querySelector("#wheel");
const cards = gsap.utils.toArray("#wheel article");
const step = 360 / cards.length;
const radius = 220;
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
gsap.set(wheel, { transformStyle: "preserve-3d", transformPerspective: 1100 });
cards.forEach(function (card, index) {
gsap.set(card, {
rotateY: index * step,
z: radius,
force3D: true,
});
});
const proxy = document.createElement("div");
const rot = { y: -20 };
function render() {
gsap.set(wheel, { rotateY: rot.y });
}
render();
const spin = reduce
? null
: gsap.to(rot, {
y: "+=360",
duration: 28,
ease: "none",
repeat: -1,
onUpdate: render,
});
if (window.Draggable) {
Draggable.create(proxy, {
trigger: wheel,
type: "x",
onPress: function () {
if (spin) spin.pause();
this.startY = rot.y;
gsap.set(proxy, { x: 0 });
this.update();
},
onDrag: function () {
rot.y = this.startY + this.x * 0.4;
render();
},
});
}