Radial slider in RaphaëlJS

💡
This article is from 2016. Code samples may no longer work.

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

💡
Don't copy and paste this stuff, proper source code further down. This is just for illustrative purposes

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 the second example, I've added a color change effect in the Red to Green part of the Hue spectrum.

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.