Wheel of Fortune with CSS
Mads Stoumann
Posted on May 23, 2024
A "Wheel of Fortune" component just popped up in my feed. I always spin, but never win! Anyway, this type of component is often built with <canvas>
, so I thought I'd write a tutorial on how to make it in CSS. For the interactivity, you still have to use JavaScript.
Here's what we'll be building:
The markup
For the wedges, we'll be using a simple list:
<ul class="wheel-of-fortune">
<li>$1000</li>
<li>$2000</li>
<li>$3000</li>
<li>$4000</li>
<li>$5000</li>
<li>$6000</li>
<li>$7000</li>
<li>$8000</li>
<li>$9000</li>
<li>$10000</li>
<li>$11000</li>
<li>$12000</li>
</ul>
OK, so we have a list of numbers. Now, let's set some initial styles:
:where(.ui-wheel-of-fortune) {
--_items: 12;
all: unset;
aspect-ratio: 1 / 1;
background: crimson;
container-type: inline-size;
direction: ltr;
display: grid;
place-content: center start;
}
First is a variable we'll be using to control the amount of items. As the list has 12 items, we set --_items: 12;
.
I set the container-type
so we can use container-query units (more on that later), then a grid with content placed "left center". This gives us:
OK, doesn't look like much, let's look into the wedges:
li {
align-content: center;
background: deepskyblue;
display: grid;
font-size: 5cqi;
grid-area: 1 / -1;
list-style: none;
padding-left: 1ch;
transform-origin: center right;
width: 50cqi;
}
Instead of position: absolute
we "stack" all the <li>
in the same place in the grid using grid-area: 1 / -1
. We set the transform-origin
to center right
, meaning we'll rotate the wedge around that axis.
So, now we have:
Because all the elements are stacked, we can only see the last.
Let's do something about that. First, we'll add an index variable to each wedge:
li {
&:nth-of-type(1) { --_idx: 1; }
&:nth-of-type(2) { --_idx: 2; }
&:nth-of-type(3) { --_idx: 3; }
&:nth-of-type(4) { --_idx: 4; }
&:nth-of-type(5) { --_idx: 5; }
/* etc. */
}
With that we only need to add one more line of CSS:
li {
rotate: calc(360deg / var(--_items) * calc(var(--_idx) - 1));
}
Getting there! Let's use the same variables to create some color variations:
li {
background: hsl(calc(360deg / var(--_items) *
calc(var(--_idx))), 100%, 75%);
}
A Slice of π
For the height of the wedges we need the circumference of the circle divided by the amount of items. As you might recall from school, the circumference of a circle is:
C=2πr
Because we're using container-units, the radius is 50cqi
, so the formula we need in CSS is:
li {
height: calc((2 * pi * 50cqi) / var(--_items));
}
Isn't it just cool that we have pi in CSS now?!
Now, let's add a simple clip-path
to each wedge. We'll start at the top left corner, move to the right center, then back to left bottom:
li {
clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
}
Let's deduct a little from the edges:
li {
clip-path: polygon(0% -2%, 100% 50%, 0% 102%);
}
Not sure, if there's a mathematical correct way to do this?
Anyway, now we just need to add border-radius: 50%
to the wrapper:
Hmm, not good. Let's use a clip-path
instead, with inset
and round
:
.wheel-of-fortune {
clip-path: inset(0 0 0 0 round 50%);
}
Much better:
And because we used container-units for the wedges and the font-size
, it's fully responsive!
Make it spin
Now, let's add a spin-<button>
(see CSS in code-example below) and trigger a spin using JavaScript:
function wheelOfFortune(selector) {
const node = document.querySelector(selector);
if (!node) return;
const spin = node.querySelector('button');
const wheel = node.querySelector('ul');
let animation;
let previousEndDegree = 0;
spin.addEventListener('click', () => {
if (animation) {
animation.cancel(); // Reset the animation if it already exists
}
const randomAdditionalDegrees = Math.random() * 360 + 1800;
const newEndDegree = previousEndDegree + randomAdditionalDegrees;
animation = wheel.animate([
{ transform: `rotate(${previousEndDegree}deg)` },
{ transform: `rotate(${newEndDegree}deg)` }
], {
duration: 4000,
direction: 'normal',
easing: 'cubic-bezier(0.440, -0.205, 0.000, 1.130)',
fill: 'forwards',
iterations: 1
});
previousEndDegree = newEndDegree;
});
}
Instead of adding and removing a css-class and updating a @property
with a new rotation-angle, I opted for the simplest solution: The Web Animations API!
Full code is here:
UPDATE: The shape-master, Temani Atif, has provided a much more elegant way to create the wedges using
tan
andaspect-ratio
(see comments below).
More ideas
I encourage you to play around with other styles! Maybe add a dotted border?
Posted on May 23, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.