Radial slider in RaphaëlJS
With Apple's new "Bedtime" feature on IOS utilising a radial slider with 2 control points, I thought I'd try my hand at developing a radial slider for the web - as an alternative to the usual horizontal slider. This slider will only have 1 control point, but could be easily modified to add a second.
My goto for this project is RaphaëlJS. It "just works" out of the box with touchscreen devices and on older browsers.
The slider
We'll be producing this:
Project Setup
For this, we're using NPM and Node as a package manager, and as a web server. Go ahead and setup a new directory somewhere, and type the following commands inside it:
Now load up your favourite text editor and create 3 new files: index.html, dial.js and server.js
Inside server.js we setup the connect http middleware framework and register the serve-static middleware.
Inside index.html - some basic scaffolding. I'm using a thin Roboto variant from google in the dial.
<html>
<head>
<script src="node_modules/raphael/raphael.min.js"></script>
<script src="dial.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100" rel="stylesheet">
</head>
<body>
<div id="dial" style="width: 500px; height: 500px"></div>
</body>
</html>
If you type the following into a terminal:
you should get a webserver on port 8080 that you can navigate to in your browser.
The Radial Slider Component Explained
The radial slider is made up of 4 parts:
- The "donut" background element
- The "arc" that represents the current percentage
- The "drag handle"
- The "text"
The donut is pretty basic. It's just a circle with a thick stroke.
The drag handle position however, and the arc require a tiny bit of maths. We'll need a couple of helpers.
The Mathy Bits
Get cartesian co-ordinates from angle
This takes a percentage (from 0 to 100), converts it straight into radians, and finds the x and y co-ords around the circumference of any given circle. Note that we're subtracting Math.PI / 2 because we want to rotate it 90 degrees left to put the origin at the top as opposed to on the right.
Draw a circular arc
Drawing a circular arc is a little more complicated. Thankfully, RaphaëlJS wraps SVG for us, so we can put together an SVG path string using the elliptical arc curve commands from the SVG spec.
We can't use this as-is to draw a 100 percent arc. We can handle that special use-case in our code by segmenting it into two 50 percent arcs.
And the last "mathy bit"
We need to do a little bit of maths for the drag handle - to calculate the angle between our cursor (x and y) and an origin (the centre of the circle). Math.atan2 to the rescue.
The RaphaëlJS bits
Since we'll be using RaphaëlJS - we should really wrap our component up so we can call it in the same way as any other component - i.e. paper.circle(), paper.rect(), or in our case: paper.dial().
We do this is by using Raphael.fn to extend, and since we want to be good non-polluting JavaScript citizens - we'll use the "Revealing Module Pattern" to implement our logic, as below:
The Radial Slider
Without further ado, here it is! Throw this into dial.js
And throw some script into index.html:
<html>
<head>
<script src="node_modules/raphael/raphael.min.js"></script>
<script src="dial.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100" rel="stylesheet">
</head>
<body>
<div id="dial" style="width: 500px; height: 500px"></div>
<script>
var paper = new Raphael('dial');
var dial = paper.dial();
</script>
</body>
</html>
If you want to see how easy this is to extend, try this instead:
<html>
<head>
<script src="node_modules/raphael/raphael.min.js"></script>
<script src="dial.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100" rel="stylesheet">
</head>
<body>
<div id="dial" style="width: 500px; height: 500px"></div>
<script>
var paper = new Raphael('dial');
var dial = paper.dial({
dialColor: Raphael.hsb(0.1, 1, 0.9),
onDrag: function(percent){
var h = percent / 300;
dial.setDialColor(Raphael.hsb(h, 1, 0.9));
}
});
</script>
</body>
</html>
In Summary
Hopefully this illustrates just how easy it is to build modular components in RaphaëlJS. This example can be easily extended to include additional public methods or configuration parameters, or to include a second drag handle for the "Start Percent" with only a few lines of code.
It can also be seamlessly dropped into an existing RaphaëlJS project.