* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 40px;
width: 100%;
max-width: 900px;
margin: 20px 0;
}
h1 {
font-size: 32px;
color: #333;
margin-bottom: 10px;
text-align: center;
font-weight: 600;
}
.subtitle {
color: #666;
font-size: 15px;
margin-bottom: 30px;
text-align: center;
}
.canvas-container {
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
border-radius: 16px;
padding: 20px;
margin-bottom: 30px;
display: flex;
justify-content: center;
align-items: center;
min-height: 500px;
position: relative;
overflow: hidden;
}
#cubeCanvas {
border-radius: 12px;
cursor: grab;
}
#cubeCanvas:active {
cursor: grabbing;
}
.controls {
background: #f8f9fa;
border-radius: 12px;
padding: 25px;
margin-bottom: 20px;
}
.controls h2 {
font-size: 20px;
color: #667eea;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.control-group {
margin-bottom: 20px;
}
.control-group label {
display: block;
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.slider-container {
display: flex;
align-items: center;
gap: 15px;
}
input[type="range"] {
flex: 1;
height: 6px;
border-radius: 3px;
background: #e0e0e0;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
border: none;
}
.slider-value {
font-size: 16px;
font-weight: 600;
color: #667eea;
min-width: 50px;
text-align: center;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
padding: 12px 20px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
flex: 1;
justify-content: center;
min-width: 120px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn-secondary {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
}
.info-box {
background: #f0f4ff;
border-left: 4px solid #667eea;
padding: 20px;
border-radius: 8px;
}
.info-box h3 {
color: #667eea;
margin-bottom: 10px;
font-size: 18px;
display: flex;
align-items: center;
gap: 8px;
}
.info-box ul {
margin-left: 20px;
color: #555;
line-height: 1.8;
}
.info-box li {
margin: 8px 0;
}
@media (max-width: 768px) {
.canvas-container {
min-height: 400px;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
}
const canvas = document.getElementById('cubeCanvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = 600;
canvas.height = 500;
// Cube properties
let cubeSize = 100;
let rotationSpeed = 0.01;
let perspective = 600;
let angleX = 0;
let angleY = 0;
let angleZ = 0;
let isRotating = true;
// Mouse interaction
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
// Define cube vertices (8 corners)
const vertices = [
[-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1], // Back face
[-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1] // Front face
];
// Define cube faces (6 faces, each with 4 vertices)
const faces = [
[0, 1, 2, 3], // Back
[4, 5, 6, 7], // Front
[0, 1, 5, 4], // Bottom
[2, 3, 7, 6], // Top
[0, 3, 7, 4], // Left
[1, 2, 6, 5] // Right
];
// Face colors
const colors = [
'#667eea', // Back - Purple
'#764ba2', // Front - Dark Purple
'#f093fb', // Bottom - Pink
'#4facfe', // Top - Blue
'#43e97b', // Left - Green
'#fa709a' // Right - Red
];
// Rotate point around X axis
function rotateX(point, angle) {
const [x, y, z] = point;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return [x, y * cos - z * sin, y * sin + z * cos];
}
// Rotate point around Y axis
function rotateY(point, angle) {
const [x, y, z] = point;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return [x * cos + z * sin, y, -x * sin + z * cos];
}
// Rotate point around Z axis
function rotateZ(point, angle) {
const [x, y, z] = point;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return [x * cos - y * sin, x * sin + y * cos, z];
}
// Project 3D point to 2D
function project(point) {
const [x, y, z] = point;
const scale = perspective / (perspective + z);
const x2d = x * scale + canvas.width / 2;
const y2d = y * scale + canvas.height / 2;
return [x2d, y2d, z];
}
// Calculate face depth (for sorting)
function getFaceDepth(face, rotatedVertices) {
let avgZ = 0;
for (let i = 0; i < face.length; i++) {
avgZ += rotatedVertices[face[i]][2];
}
return avgZ / face.length;
}
// Draw the cube
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Rotate vertices
const rotatedVertices = vertices.map(vertex => {
let point = [vertex[0] * cubeSize, vertex[1] * cubeSize, vertex[2] * cubeSize];
point = rotateX(point, angleX);
point = rotateY(point, angleY);
point = rotateZ(point, angleZ);
return point;
});
// Create face data with depth
const facesWithDepth = faces.map((face, index) => ({
face: face,
depth: getFaceDepth(face, rotatedVertices),
color: colors[index]
}));
// Sort faces by depth (painter's algorithm)
facesWithDepth.sort((a, b) => a.depth - b.depth);
// Draw faces
facesWithDepth.forEach(({ face, color }) => {
ctx.beginPath();
const projectedPoints = face.map(i => project(rotatedVertices[i]));
ctx.moveTo(projectedPoints[0][0], projectedPoints[0][1]);
for (let i = 1; i < projectedPoints.length; i++) {
ctx.lineTo(projectedPoints[i][0], projectedPoints[i][1]);
}
ctx.closePath();
// Fill face
ctx.fillStyle = color;
ctx.fill();
// Draw edges
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
ctx.lineWidth = 2;
ctx.stroke();
});
// Auto-rotate
if (isRotating) {
angleX += rotationSpeed;
angleY += rotationSpeed * 0.7;
angleZ += rotationSpeed * 0.3;
}
requestAnimationFrame(draw);
}
// Mouse event handlers
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const deltaX = e.clientX - lastMouseX;
const deltaY = e.clientY - lastMouseY;
angleY += deltaX * 0.01;
angleX += deltaY * 0.01;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
// Touch events for mobile
canvas.addEventListener('touchstart', (e) => {
isDragging = true;
lastMouseX = e.touches[0].clientX;
lastMouseY = e.touches[0].clientY;
e.preventDefault();
});
canvas.addEventListener('touchmove', (e) => {
if (isDragging) {
const deltaX = e.touches[0].clientX - lastMouseX;
const deltaY = e.touches[0].clientY - lastMouseY;
angleY += deltaX * 0.01;
angleX += deltaY * 0.01;
lastMouseX = e.touches[0].clientX;
lastMouseY = e.touches[0].clientY;
e.preventDefault();
}
});
canvas.addEventListener('touchend', () => {
isDragging = false;
});
// Control handlers
const speedSlider = document.getElementById('speedSlider');
const speedValue = document.getElementById('speedValue');
const sizeSlider = document.getElementById('sizeSlider');
const sizeValue = document.getElementById('sizeValue');
const perspectiveSlider = document.getElementById('perspectiveSlider');
const perspectiveValue = document.getElementById('perspectiveValue');
const playPauseBtn = document.getElementById('playPauseBtn');
const resetBtn = document.getElementById('resetBtn');
speedSlider.addEventListener('input', (e) => {
rotationSpeed = parseFloat(e.target.value) * 0.01;
speedValue.textContent = e.target.value;
});
sizeSlider.addEventListener('input', (e) => {
cubeSize = parseInt(e.target.value);
sizeValue.textContent = e.target.value;
});
perspectiveSlider.addEventListener('input', (e) => {
perspective = parseInt(e.target.value);
perspectiveValue.textContent = e.target.value;
});
playPauseBtn.addEventListener('click', () => {
isRotating = !isRotating;
const icon = playPauseBtn.querySelector('ion-icon');
if (isRotating) {
icon.setAttribute('name', 'pause-outline');
playPauseBtn.innerHTML = '<ion-icon name="pause-outline"></ion-icon> Pause';
} else {
icon.setAttribute('name', 'play-outline');
playPauseBtn.innerHTML = '<ion-icon name="play-outline"></ion-icon> Play';
}
});
resetBtn.addEventListener('click', () => {
angleX = 0;
angleY = 0;
angleZ = 0;
speedSlider.value = 1;
speedValue.textContent = '1.0';
sizeSlider.value = 100;
sizeValue.textContent = '100';
perspectiveSlider.value = 600;
perspectiveValue.textContent = '600';
rotationSpeed = 0.01;
cubeSize = 100;
perspective = 600;
isRotating = true;
playPauseBtn.innerHTML = '<ion-icon name="pause-outline"></ion-icon> Pause';
});
// Start animation
draw();
No comments yet. Be the first!