DEV Community

spinwheelplus
spinwheelplus

Posted on

How to Build a Spin Wheel with HTML, CSS, and JavaScript

A spin wheel is a simple but useful interactive component for random selection. You can use one to pick a name, choose a classroom activity, select a prize, make a decision, or add a game-like interaction to a website.

In this tutorial, we'll build a simple spin wheel with HTML, CSS, and JavaScript from scratch.

The final version will:

  • Display a circular wheel with multiple options
  • Draw the wheel using HTML Canvas
  • Select a random winner
  • Animate the wheel with JavaScript
  • Slow the wheel down smoothly
  • Display the selected result
  • Work on desktop and mobile screens

The goal is not to build a production-ready wheel with every possible feature, but to understand the basic mechanics behind a JavaScript spin wheel.

What We're Building

Our wheel will contain several options such as:

  • Pizza
  • Burger
  • Sushi
  • Tacos
  • Pasta
  • Salad

When the user clicks Spin, the wheel will rotate several times and gradually slow down until one segment reaches the pointer.

The important part is that the random result is selected by JavaScript before the animation finishes. The animation then makes the wheel visually land on that result.

1. Create the HTML Structure

Start with a simple HTML page containing a canvas and a button.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScript Spin Wheel</title>

    <style>
        * {
            box-sizing: border-box;
        }

        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            gap: 24px;
            font-family: Arial, sans-serif;
            background: #f5f5f5;
        }

        .wheel-container {
            position: relative;
            width: min(90vw, 500px);
            aspect-ratio: 1;
        }

        canvas {
            width: 100%;
            height: 100%;
            display: block;
        }

        .pointer {
            position: absolute;
            top: -5px;
            left: 50%;
            transform: translateX(-50%);
            width: 0;
            height: 0;
            border-left: 18px solid transparent;
            border-right: 18px solid transparent;
            border-top: 32px solid #222;
            z-index: 2;
        }

        button {
            border: 0;
            padding: 12px 28px;
            border-radius: 8px;
            background: #222;
            color: white;
            font-size: 16px;
            cursor: pointer;
        }

        button:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        #result {
            min-height: 24px;
            font-size: 20px;
            font-weight: 700;
        }
    </style>
</head>

<body>

    <div class="wheel-container">
        <div class="pointer"></div>
        <canvas id="wheel"></canvas>
    </div>

    <button id="spinButton">Spin</button>

    <div id="result"></div>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The canvas is responsible for rendering the wheel, while the pointer remains fixed at the top.

Using a responsive container also allows the wheel to scale down on smaller screens.

2. Draw the Wheel with Canvas

Now we can use the Canvas API to draw the wheel.

First, define the options:

const items = [
    "Pizza",
    "Burger",
    "Sushi",
    "Tacos",
    "Pasta",
    "Salad"
];
Enter fullscreen mode Exit fullscreen mode

Each option will occupy one segment of the wheel.

If there are six items, each segment takes up:

360 / 6 = 60 degrees
Enter fullscreen mode Exit fullscreen mode

In radians, we can calculate the angle like this:

const segmentAngle = (Math.PI * 2) / items.length;
Enter fullscreen mode Exit fullscreen mode

Now create the canvas setup:

const canvas = document.getElementById("wheel");
const ctx = canvas.getContext("2d");

const size = 500;

canvas.width = size;
canvas.height = size;

const center = size / 2;
const radius = size / 2 - 10;
Enter fullscreen mode Exit fullscreen mode

Then draw each segment:

const colors = [
    "#ff6b6b",
    "#ffd93d",
    "#6bcB77",
    "#4d96ff",
    "#845ec2",
    "#ff9671"
];

function drawWheel(rotation = 0) {
    ctx.clearRect(0, 0, size, size);

    for (let i = 0; i < items.length; i++) {
        const startAngle =
            rotation + i * segmentAngle;

        const endAngle =
            startAngle + segmentAngle;

        ctx.beginPath();
        ctx.moveTo(center, center);

        ctx.arc(
            center,
            center,
            radius,
            startAngle,
            endAngle
        );

        ctx.closePath();

        ctx.fillStyle = colors[i % colors.length];
        ctx.fill();

        ctx.strokeStyle = "#ffffff";
        ctx.lineWidth = 3;
        ctx.stroke();

        // Draw text
        const textAngle =
            startAngle + segmentAngle / 2;

        const textRadius = radius * 0.65;

        const textX =
            center + Math.cos(textAngle) * textRadius;

        const textY =
            center + Math.sin(textAngle) * textRadius;

        ctx.save();

        ctx.translate(textX, textY);
        ctx.rotate(textAngle);

        ctx.fillStyle = "#222";
        ctx.font = "bold 18px Arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";

        ctx.fillText(items[i], 0, 0);

        ctx.restore();
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, draw the initial wheel:

drawWheel();
Enter fullscreen mode Exit fullscreen mode

At this point, we already have a functional visual wheel.

3. Choose a Random Winner

The next step is selecting the winning segment.

A simple approach is to generate a random integer between 0 and the last item index:

function getRandomIndex() {
    return Math.floor(Math.random() * items.length);
}
Enter fullscreen mode Exit fullscreen mode

For a six-item wheel, this produces a number from 0 to 5.

For example:

const winnerIndex = getRandomIndex();

console.log(items[winnerIndex]);
Enter fullscreen mode Exit fullscreen mode

This is enough for a normal interactive website where the wheel is being used for casual random selection.

If you were building a security-sensitive or high-stakes randomization system, you would want a stronger source of randomness instead of relying on Math.random().

4. Calculate the Target Rotation

Selecting a winner is only half of the problem.

We also need to make the wheel visually stop with that segment underneath the pointer.

The center of the winning segment is:

const winnerAngle =
    winnerIndex * segmentAngle +
    segmentAngle / 2;
Enter fullscreen mode Exit fullscreen mode

Because our pointer is at the top of the wheel, we need to rotate the wheel so the winning segment reaches the top position.

We can calculate a target angle like this:

const pointerAngle = -Math.PI / 2;

const targetRotation =
    pointerAngle -
    winnerAngle;
Enter fullscreen mode Exit fullscreen mode

Then add several complete rotations:

const fullSpins = 5;

const finalRotation =
    targetRotation +
    fullSpins * Math.PI * 2;
Enter fullscreen mode Exit fullscreen mode

Adding several full rotations makes the animation look much more natural than simply rotating directly to the winner.

5. Add Smooth Deceleration

A common mistake when creating a spin wheel is using a constant rotation speed.

A real-looking wheel should accelerate quickly and then gradually slow down.

We can use an easing function:

function easeOutCubic(t) {
    return 1 - Math.pow(1 - t, 3);
}
Enter fullscreen mode Exit fullscreen mode

The function receives a value between 0 and 1.

At the beginning:

t = 0
Enter fullscreen mode Exit fullscreen mode

At the end:

t = 1
Enter fullscreen mode Exit fullscreen mode

The easing function makes the rotation move quickly at first and slow down near the end.

Now create the animation:

let rotation = 0;
let spinning = false;

function spin() {
    if (spinning) {
        return;
    }

    spinning = true;

    const spinButton =
        document.getElementById("spinButton");

    spinButton.disabled = true;

    const result =
        document.getElementById("result");

    result.textContent = "";

    const winnerIndex =
        getRandomIndex();

    const winnerAngle =
        winnerIndex * segmentAngle +
        segmentAngle / 2;

    const pointerAngle =
        -Math.PI / 2;

    const fullSpins = 5;

    const targetRotation =
        pointerAngle -
        winnerAngle +
        fullSpins * Math.PI * 2;

    const startRotation = rotation;
    const totalRotation =
        targetRotation - startRotation;

    const duration = 4000;
    const startTime = performance.now();

    function animate(currentTime) {
        const elapsed =
            currentTime - startTime;

        const progress =
            Math.min(elapsed / duration, 1);

        const eased =
            easeOutCubic(progress);

        rotation =
            startRotation +
            totalRotation * eased;

        drawWheel(rotation);

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            spinning = false;
            spinButton.disabled = false;

            result.textContent =
                `Winner: ${items[winnerIndex]}`;
        }
    }

    requestAnimationFrame(animate);
}
Enter fullscreen mode Exit fullscreen mode

Connect the function to the button:

document
    .getElementById("spinButton")
    .addEventListener("click", spin);
Enter fullscreen mode Exit fullscreen mode

Now the wheel can spin, slow down, and announce the winner.

6. Why Use requestAnimationFrame?

For browser animations, requestAnimationFrame() is generally a better choice than repeatedly calling a function with setInterval().

The browser can synchronize the animation with its rendering cycle.

That makes it useful for things such as:

  • Canvas animations
  • Game interfaces
  • Interactive visualizations
  • Progress animations
  • Spinning wheels

It also gives us a timestamp that we can use to calculate animation progress.

7. Make the Wheel Responsive

The canvas itself is 500 × 500 pixels, but CSS allows it to scale with the container:

.wheel-container {
    width: min(90vw, 500px);
    aspect-ratio: 1;
}
Enter fullscreen mode Exit fullscreen mode

This means the wheel can shrink on a mobile screen while maintaining its square shape.

For more advanced implementations, you can also account for device pixel ratio so the canvas remains sharp on high-density displays.

For example:

const dpr = window.devicePixelRatio || 1;

canvas.width = size * dpr;
canvas.height = size * dpr;

canvas.style.width = `${size}px`;
canvas.style.height = `${size}px`;

ctx.scale(dpr, dpr);
Enter fullscreen mode Exit fullscreen mode

This becomes especially useful when building a polished production-quality wheel.

Complete Example

The main concepts are now in place:

  1. Create a canvas.
  2. Divide the circle into equal segments.
  3. Draw each segment.
  4. Select a random winner.
  5. Calculate the target rotation.
  6. Add several full rotations.
  7. Animate using requestAnimationFrame().
  8. Apply easing so the wheel slows down naturally.
  9. Display the selected item.

From here, you can add many other features.

For example:

  • Sound effects
  • Confetti
  • Custom colors
  • Images inside segments
  • Remove-winner mode
  • Spin history
  • Custom fonts
  • Weighted probabilities
  • Saved wheels
  • Shareable wheel URLs
  • Embedding the wheel into another website

Building a Production-Ready Spin Wheel

A basic JavaScript spin wheel is relatively easy to create, but a production-ready version requires more attention to user experience.

You need to think about how the wheel behaves when someone clicks Spin repeatedly, how long the animation should last, how text fits into small segments, how the wheel performs on mobile devices, and how the final result stays synchronized with the visual animation.

You also need to consider accessibility, responsive layouts, touch interaction, audio controls, and state management.

That's where a simple Canvas experiment starts turning into a complete interactive product.

If you want to try a ready-to-use version instead of building everything from scratch, you can explore SpinWheelPlus, an online toolkit for customizable spin wheels and random selection tools.

Conclusion

Building a spin wheel with HTML, CSS, and JavaScript is a great small project for learning browser animation.

The most important concept is separating the random result from the visual animation.

JavaScript first determines which item should win. The animation then rotates the wheel until that item reaches the pointer.

Once you understand that relationship, you can build much more advanced versions with custom themes, sounds, effects, saved configurations, and interactive controls.

A spin wheel may look simple, but it combines several useful web development concepts: Canvas drawing, trigonometry, random selection, animation timing, easing functions, responsive design, and user interaction.

That's what makes it a fun project to build — and a useful component to reuse in many different types of websites.

Top comments (0)