Creating a wheel of fortune

Check out the demo here to be excited about what you can build✨✨https://tacowheel.netlify.app/

A couple of weeks ago, I wanted to explore the magic of SVG✨ so I thought of creating a little sample of a wheel of fortune taking into account SVG collisions using GSAP since it’s a big deal in the SVG community, to make it more fun, I consumed an API that returns taco recipes to help you choose what kind of tacos to eat on taco Tuesday 🌮.

So let’s jump on this mini tutorial on how to build a wheel of fortune

The beginning of the project

Since I’m in love with vite, I usedvite create appto start a react app with vite under the hood to make it fast! besides that, I installed gsap, recharts (which I will explain in a minute), and react-uuid (to generate unique ids)

Creating the wheel

I tried to create the wheel using handcrafted SVG, but I kinda cheat on this part, I saw thisreally cool repothat does kinda what I want and decided to copy its wheel functionality usingrechartswhich is a react library for charts, so here’s the code of how I created the wheel (which is a pie chart but don’t tell anyone):

//we want fixed width and height to match the arrow position that we will be creating later
<PieChart width={400} height={400} style={{ margin: "0 auto" }}>
    <Pie
        /*this is an array of items with the next structure
        [{
          id: uuid(),
          name: sliceName,
          times: 1, // this is to indicate the value of the slice
          background: randomColorFunction,
        },...]
        */
        data={items}
        labelLine={false}
        label={renderCustomizedLabel}
        fill="#8884d8"
        dataKey="times" // should match the 'times' key
        isAnimationActive={false}
        redraw={false} // to not recalculate the position of the slices after spinning
    >
        {items.map((el, index) => (
        <Cell key={`cell-${index}`} fill={el.background} />
        ))}
    </Pie>
</PieChart>

now, the label on it has a function to render the actual text that is positioned inside the slice, we calculate the x and y coordinates to position the text, so that function looks something like this:

const renderCustomizedLabel = ({
    cx,
    cy,
    midAngle,
    innerRadius,
    outerRadius,
    index,
  }) => {
    const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
    const x = cx + radius * Math.cos(-midAngle * RADIAN);
    const y = cy + radius * Math.sin(-midAngle * RADIAN);
    return (
      <text
        x={x}
        y={y}
        textAnchor="middle"
        dominantBaseline="central"
        fontSize={calcSize('fontsize')}
      >
        {name}
      </text>
    );
  };

after creating this component you should have something like this: wheel image

Making the wheel spin

to make the wheel spin, I use the next function which incorporates the magic of GSAP

let pos = 0;
async function rotate() {
    let x = randomNumber(1080, 1800); //between 4-6 wheel turns
    pos = pos + x;
    await gsap.to(".recharts-pie", // recharts automatically adds this class to the pie chart
     {
      duration: 1,
      rotation: pos,
      transformOrigin: "50% 50%",
      ease: " power2. ease-in-out",
    });
  }

Adding an arrow to identify the winner

Now the fun part begins, after having the wheel spinning, we want to check which slice is the winner, to do that, I added alittle triangle created on cssand then I assign it an id of #arrow to it

then, to check the collision, I modified the rotate function defined before to check for the collision

const [winner, setWinner] = useState(undefined);
async function rotate() {
    let x = randomNumber(1080, 1800); //between 2-4 wheel turns
    pos = pos + x;
    await gsap.to(".recharts-pie", {
      duration: 1,
      rotation: pos,
      transformOrigin: "50% 50%",
      ease: " power2. ease-in-out",
    });
    //get all the pie slices by the classname
    let pieSectors = document.getElementsByClassName("recharts-pie-sector");
    Array.from(pieSectors).forEach((sector, index) => {
        // iterate through them to check which one is hitting the arrow
      if (Draggable.hitTest(sector, "#arrow", '50px')) {
        setWinner(items[index]);
      }
    });
  }

One thing to notice is that the last param of the hitTest function is a threshold which you can indicate how many pixels can be overlapping between the two components to count it as a hit

Takeaways of this project

you can check the source code inhereand a little demo of the app inhereI added some media queries so the wheel can be playable on all the sizes and a button to call the rotate function

Also, some notes:

  • the smaller the wheel, the harder it is to check for collisions since a lot of pie slices will overlap with each other
  • if you start adding more slices, you will have the same overlapping problem
  • right now the demo has like a 95% accuracy

Sources

cd …