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 used vite create app to 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 this really cool repo that does kinda what I want and decided to copy its wheel functionality using recharts which 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:
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 a little triangle created on css and 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 in here and a little demo of the app in here I 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
Top comments (0)