JavaScript + CSS 3D Wheel of Fortune

Talking about Wheel of Fortune game, Css, Html and Javascript.

I already published ways to display a wheel of fortune using Phaser, but this time I am going to build a 3D wheel of fortune using only JavaScript and CSS.

You can not only see it in action here, but also watch a full video where I build it from scratch, step by step. Please subscribe to my brand new YouTube channel, where I am going to post a lot of tutorials and experiments.

Back to the wheel of fortune, instead of drawing a flat circle and rotating it, I decided to create a cylinder made of flat divs placed around a central axis, a bit like a carousel or a 3D slot machine.

Each tile represents one sector of the wheel. When you spin it, the entire wheel rotates around the Y axis, revealing each tile one after another.

Let’s see it in action:

Press “Spin” button and see what happens.

Tiles are a series of div with a bit of trigonometry.

By giving the container a perspective and telling the browser to preserve the 3D context, the result looks like a real rotating object.

When the wheel spins, you can actually see the tiles move in depth, disappear on one side, and reappear on the other, just like a real-life drum or carousel.

The wheel doesn’t physically “spin” each tile individually: it’s the whole cylinder that rotates around its central Y axis.
When the “SPIN” button is pressed, a few things happen behind the scenes:

– A random result is chosen, so the wheel knows where it should stop.

– A random number of full rotations is added, so it won’t stop too soon and will look naturally unpredictable.

– A CSS transition is applied to the entire wheel, changing its rotation angle smoothly over a few seconds.

    While the transition runs, the wheel continuously updates its visual rotation thanks to the browser’s 3D rendering engine.
    When the animation ends, a simple trick resets the wheel’s rotation to the final resting position, making sure the next spin starts from there without accumulating extra rotations.

    Look at the source code, in a single HTML file:

    HTML
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Wheel of fortune in JavaScript + CSS</title>
        <style>
            body {
                display: grid;
                place-items: center;
                height: 100vh;
                margin: 0;
                background-color: #1f1f1f;
            }
            #wheelcontainer {
                position: relative;
                perspective: 1000px;
            }
            #wheel {
                width: 100%;
                height: 100%;
                position: absolute;
                transform-style: preserve-3d;
            }
            .wheeltile {
                position: absolute;
                border: 1px solid black;
                font-size: 80px;
                font-family: sans-serif;
                font-weight: bold;
                display: grid;
                place-items: center;
                opacity: 0.8;
            }
            #spin {
                font-size: 24px;
                cursor: pointer;
            }
        </style>
    </head>
    <body>
        <div id="wheelcontainer">
            <div id="wheel"></div>
        </div>
        <button id="spin">SPIN</button>
        <script>
    
            // wheel elements
            const wheelElement = document.getElementById('wheel');
            const containerElement = document.getElementById('wheelcontainer');
            const spinButton = document.getElementById('spin');
    
            // amount of tiles
            const tiles = 10;
    
            // tile size
            const tileWidth = 200;
            const tileHeight = 150;
    
            // circle geometry and trigonometry
            const deltaAngle = 360 / tiles;
            const z = Math.round((tileWidth / 2) / Math.tan(Math.PI / tiles));
    
            // is the wheel spinning?
            let spinning = false;
    
            // final wheel angle;
            let finalAngle;
            
            // set element styles dynamically
            containerElement.style.width = tileWidth + 'px';
            containerElement.style.height = tileHeight + 'px';
            wheelElement.style.transform = 'translateZ(' + (z * -1) + 'px)';
    
            // build all tiles
            for (let i = 0; i < tiles; i ++) {
    
                // each tile is a div
                const newTile = document.createElement('div');
    
                // add wheeltile class to new tile
                newTile.classList.add('wheeltile');
    
                // set tile width and height
                newTile.style.width = tileWidth + 'px';
                newTile.style.height = tileHeight + 'px';
    
                // assign the tile a random color
                newTile.style.backgroundColor = randomColor();
    
                // transform the tile
                newTile.style.transform = 'rotateY(' + deltaAngle * i + 'deg) translateZ(' + z + 'px)';
    
                // put some content in the tile
                newTile.innerText = i;
    
                // append tile to wheel
                wheelElement.appendChild(newTile);
            }
    
            // function to randomly generate a random light color
            function randomColor() {
                const r = Math.floor(Math.random() * 155) + 100;
                const g = Math.floor(Math.random() * 155) + 100;
                const b = Math.floor(Math.random() * 155) + 100;
                return 'rgb(' + r + ',' + g + ',' + b + ')';
            }
    
            // when the button is clicked...
            spinButton.addEventListener('click', () => {
    
                // if the wheel is spinning, do nothing
                if (spinning) {
                    return;
                }
    
                spinning = true;
    
                // toss a random result
                const randomResult = Math.floor(Math.random() * tiles);
                const randomRounds = 5 + Math.floor(Math.random() * 15);
                const randomSeconds = 5 + Math.floor(Math.random() * 5);
    
                // just to show you I am right :)
                spinButton.innerText = 'Will end on tile ' + randomResult;
    
                // compute the final angle
                finalAngle = randomResult * deltaAngle + 360 * randomRounds;
    
                // apply the proper style to wheel element
                wheelElement.style.transition = 'transform ' + randomSeconds + 's';
                wheelElement.style.transform = 'translateZ(-' + z + 'px) rotateY(-' + finalAngle + 'deg)';
            });
    
            // function to be executed when the rotation is over
            wheelElement.addEventListener('transitionend', () => {
                spinning = false;
    
                // reset button and wheel transform
                spinButton.innerText = 'SPIN';
                finalAngle = finalAngle % 360;
                wheelElement.style.transition = 'none';
                wheelElement.style.transform = 'translateZ(-' + z + 'px) rotateY(-' + finalAngle + 'deg)';
            });
        </script>
    </body>
    </html>

    Now you can copy/paste into your projects and have your spinning 3D wheel of fortune.