Dany the spacecowboy
Posted on April 27, 2021
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
Posted on April 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
July 19, 2022