JSFiddle - React, Tailwind, and code Playground
by HDL52
HTML
<ul class="threeD-button-set">
<li><button>New Game</button></li>
<li><button>Continue</button></li>
<li><button>Online</button></li>
<li><button>Settings</button></li>
<li><button>Quit</button></li>
</ul>
CSS
@import url("https://fonts.googleapis.com/css2?family=Audiowide&family=Ma+Shan+Zheng&family=Roboto:wght@400;700&display=swap");
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
.threeD-button-set {
--y: ;
--x: ;
--distance: 1px;
--theme: hsl(270 100% 50%);
--theme-bg: hsl(270 100% 50% / 25%);
--theme-bg-hover: hsl(270 100% 50% / 40%);
--theme-text: white;
--theme-shadow: hsl(270 100% 10% / 25%);
--_max-rotateY: 10deg;
--_max-rotateX: 15deg;
--_btn-bg: var(--theme-bg);
--_btn-bg-hover: var(--theme-bg-hover);
--_btn-text: var(--theme-text);
--_btn-text-shadow: var(--theme-shadow);
--_bounce-ease: cubic-bezier(0.5, 1.75, 0.75, 1.25);
@media (--dark) {
--theme: hsl(180 100% 50%);
--theme-bg: hsl(180 100% 71% / 25%);
--theme-bg-hover: hsl(180 100% 50% / 40%);
--theme-shadow: hsl(180 100% 10% / 25%);
}
@media (--HDcolor) {
@supports (color: color(display-p3 0 0 0)) {
--theme: color(display-p3 0.4 0 0.9);
}
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
overflow: hidden;
perspective: 40vw;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at center, #111, #000);
}
.threeD-button-set {
margin: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
transform-style: preserve-3d;
will-change: transform;
animation: rotate-y 5s ease-in-out infinite;
transform: rotateX(var(--x)) rotateY(var(--y));
}
@media (--motionOK) {
.threeD-button-set {
will-change: transform;
transition: transform 0.1s ease;
}
}
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
.threeD-button-set>li {
display: inline-flex;
position: relative;
transform-style: preserve-3d;
}
.threeD-button-set button {
...
JavaScript
document.addEventListener('DOMContentLoaded', () => {
const menu = document.querySelector('.threeD-button-set');
let menuRect = menu.getBoundingClientRect();
const { matches: motionOK } = window.matchMedia('(prefers-reduced-motion: no-preference)');
const getAngles = (clientX, clientY) => {
const { x, y, width, height } = menuRect;
const dx = clientX - (x + 0.5 * width);
const dy = clientY - (y + 0.5 * height);
return { dx, dy };
};
const updateAngles = ({ clientX, clientY }) => {
const { dx, dy } = getAngles(clientX, clientY);
const xAngle = Math.max(Math.min(dy / 20, 15), -15);
const yAngle = Math.max(Math.min(dx / 20, 15), -15);
menu.style.setProperty('--x', `${xAngle}deg`);
menu.style.setProperty('--y', `${yAngle}deg`);
};
if (motionOK) {
window.addEventListener('mousemove', updateAngles);
window.addEventListener('resize', () => {
menuRect = menu.getBoundingClientRect();
});
}
});