Hey there, fellow developers! 👋 Today I'm excited to share a fun weekend project I built: a fully interactive 3D dice roller with roll history tracking. It's a perfect blend of visual appeal and practical functionality that you can use for board games, tabletop RPGs, or just when you need a quick random number.
Let's break down how to build this project from scratch using vanilla HTML, CSS, and JavaScript. No frameworks needed! 🚀
Demo
You can check out the live demo here before we dive into the code.
Features
- Realistic 3D dice with smooth rolling animation
- Customizable dice color with automatic text contrast adjustment
- Complete roll history with timestamps
- Clean, sci-fi inspired UI
- Fully responsive design
The HTML Structure
First, let's set up our HTML foundation. We'll need elements for the dice, controls, and history section:
</span>
lang="en">
charset="UTF-8">
name="viewport" content="width=device-width, initial-scale=1.0">
3D Dice Roller
rel="stylesheet" href="styles.css">
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
class="container">
3D Dice Roller
class="dice-wrapper">
class="dice-container">
id="dice" class="dice">
class="face front">1
class="face back">2
class="face right">3
class="face left">4
class="face top">5
class="face bottom">6
id="roll-btn" class="roll-btn">Roll Dice
class="color-picker">
for="dice-color">Dice Color:
type="color" id="dice-color" value="#ff6f61">
id="result" class="result">
class="history">
Roll History
id="history-list" class="history-list">
id="clear-history" class="clear-btn">Clear History
<span class="na">src="script.js">
Enter fullscreen mode
Exit fullscreen mode
The HTML creates a structure with:
A container for our 3D dice with all six faces defined
A roll button to trigger the animation
A color picker for customization
A results display area
A history section that logs all rolls
Styling with CSS: Creating the 3D Effect
Now for the fun part - making our dice actually look 3D! We'll use CSS transforms to position each face of the dice in 3D space:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Orbitron', sans-serif;
background: linear-gradient(135deg, #1e1e2f, #2a2a4a);
color: #fff;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
text-align: center;
padding: 20px;
max-width: 600px;
width: 100%;
}
h1 {
font-size: 2.5rem;
margin-bottom: 20px;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.dice-wrapper {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0;
}
.dice-container {
perspective: 1000px;
}
.dice {
width: 150px;
height: 150px;
position: relative;
transform-style: preserve-3d;
transition: transform 1s ease-out;
}
.face {
position: absolute;
width: 150px;
height: 150px;
background: #ff6f61; /* Default color */
border: 2px solid #fff;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
font-weight: bold;
color: #fff; /* Default text color */
box-shadow: 0 0 15px rgba(255, 111, 97, 0.7);
}
/* Face positions for a true 3D cube */
.front { transform: translateZ(75px); }
.back { transform: translateZ(-75px) rotateY(180deg); }
.right { transform: translateX(75px) rotateY(90deg); }
.left { transform: translateX(-75px) rotateY(-90deg); }
.top { transform: translateY(-75px) rotateX(90deg); }
.bottom { transform: translateY(75px) rotateX(-90deg); }
/* Stable states maintaining 3D structure */
.show-1 { transform: rotateX(0deg) rotateY(0deg); }
.show-2 { transform: rotateX(0deg) rotateY(180deg); }
.show-3 { transform: rotateX(0deg) rotateY(-90deg); }
.show-4 { transform: rotateX(0deg) rotateY(90deg); }
.show-5 { transform: rotateX(-90deg) rotateY(0deg); }
.show-6 { transform: rotateX(90deg) rotateY(0deg); }
/* Rolling animation */
@keyframes roll {
0% { transform: rotateX(0deg) rotateY(0deg); }
100% { transform: rotateX(720deg) rotateY(720deg); }
}
.rolling {
animation: roll 1s ease-out forwards;
}
.roll-btn, .clear-btn {
padding: 15px 30px;
font-size: 1.2rem;
border: none;
border-radius: 10px;
background: #4a4e69;
color: #fff;
cursor: pointer;
transition: transform 0.2s, background 0.3s;
margin: 10px;
}
.roll-btn:hover, .clear-btn:hover {
transform: scale(1.05);
background: #5c627d;
}
.color-picker {
margin: 20px 0;
}
.color-picker label {
font-size: 1.2rem;
margin-right: 10px;
}
#dice-color {
vertical-align: middle;
cursor: pointer;
}
.result {
margin: 20px 0;
font-size: 1.5rem;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.8);
}
.history {
margin-top: 30px;
}
h2 {
font-size: 1.5rem;
margin-bottom: 10px;
}
.history-list {
max-height: 200px;
overflow-y: auto;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px;
}
.history-item {
padding: 8px;
margin-bottom: 5px;
background: rgba(255, 255, 255, 0.05);
border-radius: 5px;
text-align: left;
font-size: 1rem;
}
Enter fullscreen mode
Exit fullscreen mode
The CSS magic happens through:
The .dice-container with perspective: 1000px that creates our 3D space
The .dice element with transform-style: preserve-3d that maintains the 3D positioning
Individual face positioning using translateZ() and rotations
Classes like .show-1 through .show-6 that control which face is shown
The rolling animation that creates the tumbling effect
I particularly love the gradient background that gives the whole application that futuristic sci-fi feel. The glow effects on the dice faces also add depth to the visualization.
Making It Interactive with JavaScript
Now let's breathe life into our dice with JavaScript:
const dice = document.getElementById('dice');
const rollBtn = document.getElementById('roll-btn');
const result = document.getElementById('result');
const historyList = document.getElementById('history-list');
const clearBtn = document.getElementById('clear-history');
const colorPicker = document.getElementById('dice-color');
const faces = document.querySelectorAll('.face');
let rollHistory = [];
function rollDice() {
rollBtn.disabled = true;
result.textContent = '';
dice.classList.remove('show-1', 'show-2', 'show-3', 'show-4', 'show-5', 'show-6');
dice.classList.add('rolling');
const rollResult = Math.floor(Math.random() * 6) + 1;
setTimeout(() => {
dice.classList.remove('rolling');
dice.classList.add(`show-${rollResult}`);
result.textContent = `You rolled a ${rollResult}!`;
addToHistory(rollResult);
rollBtn.disabled = false;
}, 1000);
}
function addToHistory(result) {
const timestamp = new Date().toLocaleTimeString();
rollHistory.unshift({ result, timestamp });
updateHistoryUI();
}
function updateHistoryUI() {
historyList.innerHTML = rollHistory.map(item => `
Rolled: ${item.result} at ${item.timestamp}
`).join('');
}
function clearHistory() {
rollHistory = [];
updateHistoryUI();
}
function adjustTextColor(bgColor) {
const rgb = hexToRgb(bgColor);
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
const textColor = brightness > 128 ? '#000' : '#fff';
faces.forEach(face => face.style.color = textColor);
}
function hexToRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
}
function updateDiceColor(color) {
faces.forEach(face => {
face.style.background = color;
face.style.boxShadow = `0 0 15px ${color}`;
});
adjustTextColor(color);
}
rollBtn.addEventListener('click', rollDice);
clearBtn.addEventListener('click', clearHistory);
colorPicker.addEventListener('input', (e) => updateDiceColor(e.target.value));
Enter fullscreen mode
Exit fullscreen mode
The JavaScript handles four main responsibilities:
1. The Rolling Mechanism
When the user clicks "Roll Dice," our code:
Applies a rolling animation class
Generates a random number between 1-6
Updates the dice position to show the correct face after animation
Displays the result text
2. History Tracking
Each roll is recorded with:
The dice value
A timestamp of when it occurred
These are stored in an array and displayed in the history list
3. Color Customization
The color picker allows users to:
Change the dice color in real-time
Automatically adjust text color for readability
Update the glow effect to match the selected color
4. Intelligent Contrast
One of my favorite parts is the brightness calculation that determines whether text should be black or white based on the background color:
// This is a snippet from the full code
function adjustTextColor(bgColor) {
const rgb = hexToRgb(bgColor);
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
const textColor = brightness > 128 ? '#000' : '#fff';
faces.forEach(face => face.style.color = textColor);
}
Enter fullscreen mode
Exit fullscreen mode
This uses the perceived brightness formula to ensure text remains readable regardless of background color.
The CSS Transform Magic ✨
Let's take a closer look at how the 3D effect works. Each face is positioned using CSS transforms:
/* This is a snippet of the positioning CSS */
.front { transform: translateZ(75px); }
.back { transform: translateZ(-75px) rotateY(180deg); }
.right { transform: translateX(75px) rotateY(90deg); }
.left { transform: translateX(-75px) rotateY(-90deg); }
.top { transform: translateY(-75px) rotateX(90deg); }
.bottom { transform: translateY(75px) rotateX(-90deg); }
Enter fullscreen mode
Exit fullscreen mode
To show a specific face, we rotate the entire cube. For example:
.show-1 { transform: rotateX(0deg) rotateY(0deg); }
.show-6 { transform: rotateX(90deg) rotateY(0deg); }
Enter fullscreen mode
Exit fullscreen mode
Tips for Your Implementation
When building this yourself, watch out for these common issues:
3D Perspective: If your dice doesn't look 3D, check that you've set the proper perspective value and transform-style.
Animation Timing: The rolling animation should be long enough to look realistic but short enough to not frustrate users. I found 1 second to be a good balance.
Mobile Responsiveness: Test on various screen sizes - the 3D effect should look good on all devices.
Color Contrast: When implementing custom colors, always ensure text remains readable.
Ideas for Expansion
Want to take this project further? Here are some ideas:
Add sound effects for the rolling animation
Implement different dice types (D4, D8, D12, D20)
Add an option to roll multiple dice simultaneously
Create statistics tracking for roll distributions
Save color preferences using localStorage
What I Learned
Building this project taught me a lot about:
Advanced CSS 3D transforms and animations
Dynamic UI updates with JavaScript
Color theory and accessibility considerations
Creating a cohesive user experience
Conclusion
This 3D dice roller demonstrates how modern CSS and JavaScript can create interactive, visually appealing applications without relying on heavy libraries or frameworks. It's a perfect example of making something both functional and beautiful using core web technologies.I hope you enjoyed this walkthrough! Feel free to use this code as a starting point for your own projects or as a learning resource for understanding 3D CSS transforms.Let me know in the comments if you build your own version or have questions about any part of the implementation! 🎲What other web-based tools would you like to see tutorials for? Let me know in the comments!