Spin the Wheel — a customizable roulette wheel in less than 30kb with a no-javascript fallback.
Tags: showdev, webdev, javascript, css
Published: 2021-03-22 11:07:41

*Here's the link: [missingdice.com/spin-the-wheel](https://missingdice.com/spin-the-wheel/). I'd really appreciate any feedback on improving it, i've been staring at it for a week!*

I've been building a website of simple tools for boardgame players. For various reasons there are times when you need to **roll dice**, **flip cards**, or **spin a spinner** online.

The site is for simple things like that. 

I want the site to be a success, so i first took a look at the competition and — for reasons I'll elaborate on in another post — decided that:

- the site should be as accessible as possible
- no page should be larger than `30kB`
- every tool should have a `no javascript` fallback

For this tool there we're some interestng hurdles to overcome:

---

## Satisfying Click

It was important that the spinner have a satisfying clicking sound as it spun. 

I found an `mp3` of a click, but even at less than 1 second long, it weighed `7kB`. Using it would have put me over the `30kB` budget.

I'm sure there are lots of clever ways of reducing the file size of audio. But instead I chose to generate a click with `JavaScript` and the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) Something I hadn't even heard of before now.

*Luckily for me, I know a synth enthusiast, and he explained some of the terminology to me.*

I found [this tutorial on synthesizing drum sounds](https://dev.opera.com/articles/drum-sounds-webaudio/) and tweaked the hi-hat example to fit.

This ended up being just `~1.2kB` of `js` - with room for further optimization.

---

## Creating a no-javascript version

To make the spinner work without `js` was surprisingly simple. 

If the browser has `JavaScript` disabled, instead of generating and spinning the wheel on the `client-side`, clicking `spin the wheel` submits a form…

Then the server:

- builds a spinner with the user's custom values
- randomly selects a winner
- generates in advanced a css animation that spins the wheel
- sends the html back to the client

It works surprisingly well.

*I did this with [Netlify Functions](https://www.netlify.com/products/functions/), so I'm not running an entire server for the tiny number of people who'll use the site without `js`.*

---

## Animating SVG

While animating SVG is generally fine, some browsers really struggle with it (Safari). After a lot of tinkering, it turns out the best fix is just to use seperate SVGs for each animated component, and put them each in their own `<div>` — then animate the `<div>`.

---

## Timing the clicks

The spinner spins at different rates, durations, and with a random number of rotations — that way it stays surprising and dramatic.

For the spin to be really satisfying, it needed a little ticker on top. (like on the gameshow "wheel of fortune").

That meant having "pins" around the rim of the wheel, and animating a ticker each time it "hit" a "pin."

For performance reasons I thought it would be better to calculate the timings of the animation (and click sound) in advance.

This turns out to be a seriously complicated task, and after 3 days of learning calculus I gave up. 

Instead it uses `requestAnimationFrame` and measures the current rotation of the spinner. If the rotation is between certain ranges it produces a click.

This works okay, but it does make mistakes. 

It also means the `no-javascript` version has no ticker animation.

---

## Spinning with 1000s of values

One issue was allowing people to add 1000s of values to the spinner. 

I figured there is a use case where someone might want to paste an entire spreadsheet of values to have one picked at random.

So, I decided to use a `<textarea>` as the input, with a new line for each new value. Then, if the user pastes a comma-seperated list, it will reformat it before generating the wheel.

**The big problem here is performance.**

To make it work, the spinner `<svg>` gets less complex as more values are added.

- The patterns are removed. 
- The number of pins on the rim of the wheel is capped at `60`
- The text paths become simpler.

I've only tested it on my fancy new computer, but it works fine up to around `6000` values. Feel free to test it out and let me know!

---

## Things to Improve!

- The overall look and feel could do with a polish — particularly on the alternate color-schemes.
- The click sound could do with a tweak.
- Finding an accurate way of measuring the click animation in advanced would be amazing.
- Making custom wheels embeddable as an `<iframe>` would be cool.

Let me know what you think and what can be improved?