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:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Dice Roller</title>
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>3D Dice Roller</h1>
<div class="dice-wrapper">
<div class="dice-container">
<div id="dice" class="dice">
<div class="face front">1</div>
<div class="face back">2</div>
<div class="face right">3</div>
<div class="face left">4</div>
<div class="face top">5</div>
<div class="face bottom">6</div>
</div>
</div>
</div>
<button id="roll-btn" class="roll-btn">Roll Dice</button>
<div class="color-picker">
<label for="dice-color">Dice Color: </label>
<input type="color" id="dice-color" value="#ff6f61">
</div>
<div id="result" class="result"></div>
<div class="history">
<h2>Roll History</h2>
<div id="history-list" class="history-list"></div>
<button id="clear-history" class="clear-btn">Clear History</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
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;
}
The CSS magic happens through:
- The
.dice-container
withperspective: 1000px
that creates our 3D space - The
.dice
element withtransform-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 => `
<div class="history-item">Rolled: ${item.result} at ${item.timestamp}</div>
`).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));
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);
}
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); }
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); }
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!