DEV Community

Cover image for How to create perfect CSS circle sectors
Andrei Gheorghiu
Andrei Gheorghiu

Posted on

How to create perfect CSS circle sectors

Spinning the wheel: building a jackpot roulette

As a CSS enthusiast, you’ve probably dabbled in gradients, animations, and maybe even a bit of trigonometry. But have you ever built a responsive, interactive CSS jackpot roulette? Recently, I had to tackle just that, and it turned out to be an challenging exercise in geometry, creativity, and a sprinkle of frontend magic. Let me take you through the process step by step.


The challenge: responsive highlightable sectors

The roulette needed to:

  1. Highlight the sectors as the needle pointed at them.
  2. Be fully responsive, adapting to screen size.
  3. Allow a variable number of sectors—a critical feature that ruled out using static images or SVGs.

This meant one thing: we’d need to draw the sectors dynamically and calculate their shapes and positions geometrically. Intrigued? Let’s dive in.


The setup: rotating spans around the center

To start, I used <span> elements to represent the sectors, rotating each around the center of the circle. Each sector would be clipped along its radius to avoid overlap. You can take a peek at my initial setup here.

For the rotation, I iterated through the sectors, incrementally rotating them by:

transform: rotate(calc(360deg / number_of_sectors * index));
Enter fullscreen mode Exit fullscreen mode

At this stage, everything was pretty simple. I had a circle, some rotated spans of text and rays in between them. But in order to highlight a sector I needed to draw its precise shape.


The math: highlighting a sector

The first thing I needed to calculate was the sector height based on the angle between its boundaries and the radius. The formula for the distance between two points on a circle, given the radius and angle, is:

2 * radius * Math.sin(θ / 2)
Enter fullscreen mode Exit fullscreen mode

Where θ is the angle in radians. For our evenly spaced sectors, θ becomes:

2 * Math.PI / number_of_sectors
Enter fullscreen mode Exit fullscreen mode

Plugging that in:

2 * radius * Math.sin(Math.PI / number_of_sectors)
Enter fullscreen mode Exit fullscreen mode

This gave me the precise height of each sector (or, should I say, its dimension on the axis perpendicular to its text orientation).


The clipping: precision meets aesthetic

Initially, I tried clipping each sector from the top-right corner to the left-center and then to the bottom-right corner:

clip-path: polygon(100% 0, 0 50%, 100% 100%);
Enter fullscreen mode Exit fullscreen mode

This worked… sort of. With many sectors, it was passable. But with fewer sectors—say three—it was, glaringly wrong:

Sectors clipped wrong

The fix? Calculating the exact intersection points between the sector and the circle, clipping from there to the center. After some research, I found the formulas for the segments formed on a horizontal ray by the line connecting the two points on the circle:

Image for segment formulas

  • Segment close to the center:
const m = radius * (1 - Math.cos(θ / 2))
Enter fullscreen mode Exit fullscreen mode
  • Segment farther from the center:
const n = radius * Math.cos(θ / 2)
Enter fullscreen mode Exit fullscreen mode

Just like above, θ is in radians:

2 * Math.PI / number_of_sectors
Enter fullscreen mode Exit fullscreen mode

The ratio between the two segments gave me the exact clipping point:

const clipPosition = 100 * (m + n) / m
Enter fullscreen mode Exit fullscreen mode

Plugging in to our example:

const clipPosition = Math.cos(Math.PI / number_of_sectors) * 100
Enter fullscreen mode Exit fullscreen mode

...which led to the final clip-path:

  clip-path: polygon(
    100% 0,
    ${clipPosition}% 0,
    0 50%,
    ${clipPosition}% 100%,
    100% 100%
  );
Enter fullscreen mode Exit fullscreen mode

Now the sectors aligned perfectly:
Sectors clipped correctly


Bringing it to life

I implemented the spinning logic using Vue. Clicking the center produces a random spin, while clicking on a sector spins the roulette a few times and then lands on that sector. You can check out the result here.
Code available here.


Closing thoughts

Hope you enjoyed this exploration. What’s the next CSS challenge you’re tackling? Let me know — I’m always eager to swap tricks and ideas!

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay