<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Wheel Menu with JSON Events</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 100vh;
background: #f5f5f5;
font-family: Arial, sans-serif;
}
.wheel-container {
perspective: 1000px;
width: 250px;
height: 400px;
position: relative;
border: 0px solid #000;
padding: 35px;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
right: 0;
}
.wheel {
width: 200px;
height: 350px;
position: relative;
margin: 0 auto;
transform-style: preserve-3d;
transition: transform 0.1s linear;
transform: rotateX(0deg);
}
.wheel__segment {
position: absolute;
width: 100%;
height: 40px;
top: 50%;
display: flex;
justify-content: center;
align-items: center;
background: #ddd;
border: 1px solid #aaa;
transform-origin: 50% 0;
color: #333;
font-size: 14px;
font-weight: bold;
transition: box-shadow 0.3s ease;
}
.wheel__segment span {
transform: translateZ(120px);
}
.wheel__segment:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
/* Styling for contentview */
#contentview {
position: absolute;
left: 30px;
top: 50px;
width: 400px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
font-size: 16px;
}
</style>
</head>
<body>
<div class="wheel-container">
<div class="wheel"></div>
</div>
<!-- Contentview div where item info will be shown -->
<div id="contentview">
<h2>Practical application of the source code and ideas of this article.</h2><br>
<p id="item-info">Click on an item to see its details here.</p>
</div>
<script>
(function($) {
const spinSound = new Audio('https://cdn.pixabay.com/download/audio/2025/01/19/audio_fca0fdbc60.mp3?filename=wind-swoosh-short-289744.mp3');
const $wheel = $('.wheel');
const segmentCount = 20;
const segmentAngle = 360 / segmentCount;
const wheelHeight = $wheel.height();
const radius = wheelHeight / 2;
const segmentHeight = (2 * Math.PI * radius) / segmentCount;
// Data for items on the wheel
const items = [
{
id: 1,
title: 'Item 1',
Action: 'click',
Event: () => displayContent('Item 1', 'Details of Item 1')
},
{
id: 2,
title: 'Item 2',
Action: 'dblclick',
Event: () => displayContent('Item 2', 'Details of Item 2')
},
{
id: 3,
title: 'Item 3',
Action: 'click',
Event: () => displayContent('Item 3', 'Details of Item 3')
},
{
id: 4,
title: 'Item 4',
Action: 'dblclick',
Event: () => displayContent('Item 4', 'Details of Item 4')
},
{
id: 5,
title: 'Item 5',
Action: 'click',
Event: () => displayContent('Item 5', 'Details of Item 5')
},
{
id: 6,
title: 'Item 6',
Action: 'dblclick',
Event: () => displayContent('Item 6', 'Details of Item 6')
}
];
// Extend items array to match segment count
const extendedItems = [];
for (let i = 0; i < segmentCount; i++) {
extendedItems.push(items[i % items.length]);
}
// Function to create segments on the wheel
for (let i = 0; i < segmentCount; i++) {
const angle = segmentAngle * i;
const item = extendedItems[i];
const $segment = $('<div>', {
class: 'wheel__segment',
'data-index': i
}).css({
'transform': `rotateX(${angle}deg) translateZ(${radius}px)`,
'height': segmentHeight
}).html(`<span>${item.title}</span>`).appendTo($wheel);
// Attach event handlers
$segment.on(item.Action, function() {
item.Event(); // Trigger event from JSON data
});
}
// Function to update contentview div with item details
function displayContent(title, details) {
$('#item-info').html(`<strong>${title}</strong><br>${details}`);
}
// Function to handle the size of the wheel dynamically
function changeWheelSize(width, height) {
const $container = $('.wheel-container');
const $wheel = $('.wheel');
$container.css({
width: width + 'px',
height: height + 'px'
});
$wheel.css({
width: (width - 70) + 'px',
height: (height - 70) + 'px'
});
const newWheelHeight = $wheel.height();
const newRadius = newWheelHeight / 2;
const newSegmentHeight = (2 * Math.PI * newRadius) / segmentCount;
$('.wheel__segment').each(function(i) {
const angle = segmentAngle * i;
$(this).css({
'transform': `rotateX(${angle}deg) translateZ(${newRadius}px)`,
'height': newSegmentHeight
});
});
}
// Call function to adjust wheel size
changeWheelSize(250, 500);
let currentRotation = 0;
let isDragging = false;
let startY = 0;
let lastY = 0;
let lastTime = 0;
let velocity = 0;
let animationId = null;
// Function to play sound when wheel rotates
function playSpinSound() {
spinSound.currentTime = 0;
spinSound.play();
}
// Function to update wheel rotation
function updateWheel() {
$wheel.css('transform', `rotateX(${currentRotation}deg)`);
playSpinSound();
}
// Mouse and touch event handlers for dragging
$wheel.on('mousedown touchstart', function(e) {
e.preventDefault();
isDragging = true;
startY = getEventY(e);
lastY = startY;
lastTime = performance.now();
cancelAnimationFrame(animationId);
velocity = 0;
});
$(document).on('mousemove touchmove', function(e) {
if (!isDragging) return;
e.preventDefault();
const currentY = getEventY(e);
const deltaY = currentY - lastY;
currentRotation -= deltaY * 0.5;
velocity = -deltaY / (performance.now() - lastTime) * 15;
lastY = currentY;
lastTime = performance.now();
updateWheel();
});
$(document).on('mouseup touchend', function() {
if (!isDragging) return;
isDragging = false;
if (Math.abs(velocity) > 0.5) {
applyMomentum();
}
});
function getEventY(e) {
return e.type.includes('touch') ? e.touches[0].pageY : e.pageY;
}
function applyMomentum() {
const friction = 0.96;
velocity *= friction;
if (Math.abs(velocity) > 0.5) {
currentRotation += velocity;
updateWheel();
animationId = requestAnimationFrame(applyMomentum);
}
}
})(jQuery);
</script>
</body>
</html>