get your pixels movin'
The logo does tricks:

HSLA Color Dials With mapRange()

I’ve been using hsla in more projects lately instead of rgb and hex. I find it offers more possibilities and just works better for me. In today’s tutorial, we’ll be using four dials to change the hsla background color of an element.

To do this, I used the handy mapRange() utility method and Draggable plugin from GreenSock. These are free tools available to anyone. The dials don’t go to 11. That’s a Spinal Tap joke for any readers who may be fans. Let’s spin the dial and get this thing started.

hsla() color quick review

This isn’t a tutorial about using hsla for your colors, but if you’re not familiar with it, here’s a quick overview. It’s made up of four values:

  1. Hue: The color from the color wheel. 0° and 360° are reds, 120° are greens and 240° are blues. Use any value from 0 → 360
  2. Saturation: Percentage from 0 → 100%. 0 being grayscale and 100 being fully saturated.
  3. Lightness: Percentage from 0 → 100%. 0 would be black, 100 would be white and 50% is average lightness.
  4. Alpha: Number from 0 → 1. 0 is transparent and 1 is fully opaque.

The Dial SVG elements

Nothing too exciting here. It’s a regular SVG with a few paths that act as the dial. The non-moving base is just a circle with another path for the tick marks.

    <g id="dial">
      <path d="M70,140.05A60.49,60.49,0,1,0,9.52,79.57,60.6,60.6,0,0,0,70,140.05Z" fill="#4d4d4d" fill-rule="evenodd"/>
      <path d="M70,24.07a55.42,55.42,0,0,1,46.08,24.57H23.91A55.45,55.45,0,0,1,70,24.07Z" fill="#ccc" fill-rule="evenodd"/>
      <path d="M115.48,47.76c.2.29.41.58.6.88H23.91c.2-.3.41-.59.61-.88Z" fill="#e6e6e6" fill-rule="evenodd"/>
      <path d="M70,24.07a55.5,55.5,0,1,1-55.5,55.5A55.5,55.5,0,0,1,70,24.07Zm0,.73a54.77,54.77,0,1,0,54.77,54.77A54.77,54.77,0,0,0,70,24.8Z" fill="#e6e6e6" fill-rule="evenodd"/>
      <circle cx="70" cy="29.49" r="2.15" fill="#333"/>
      <text transform="translate(70 86)">ROTATE ME</text>
    </g>

Note: you can use regular divs or images here. I enjoy working with SVGs so that’s what I went with for the tutorial.

Wiring up the dial

After first registering the Draggable plugin, we set the dial’s transformOrigin to center so it spins properly. The hslaData variable is just some text that will provide feedback as to how far we’ve rotated the dial.

gsap.registerPlugin(Draggable);
const hslaData = document.querySelector("#hslaData");
gsap.set("#dial", { transformOrigin: "center center" });

Draggable

Using GreenSock’s Draggable plugin is super easy. We simply create the dial and set a few parameters. First, we set the type to rotation. Next, we set bounds. I created the dial in the 12:00 o’clock position and we want it to rotate no more than 135 degrees in either direction. I’ve chosen a limit of 135 in this case, but you can use any values you like for your artwork.

Draggable.create("#dial", {
  type: "rotation",
  bounds: {
    minRotation: -135,
    maxRotation: 135
  },
  onDrag: updateRotation
});

Lastly, and most importantly, we call the updateRotation() function when we drag the dial. In that function, we will change the innerText to the current rotation of the dial.

function updateRotation() {
  hslaData.innerText = this.rotation.toFixed();
}

Once that is all done, we are left with a dial like this.

See the Pen HSLA Dial Tutorial Single Dial Part 1 by Craig Roblewsky (@PointC) on CodePen.

Mapping the range

Okay, we have the dial providing us with the rotation from -135° to 135° which is pretty cool, but we need to convert that position to another value. What if we want -135° to equal 0 and 135° to equal 100%? That’s exactly what we need for the saturation and lightness dials (coming up next).

To accomplish that, we use GreenSock’s super handy .mapRange() utility method. It takes 5 values: inMin, inMax, outMin, outMax and the input value we want to map. In this case, the inMin and inMax are the -135° and 135°. The output we want is outMin: 0 and outMax: 100. The value to map will be the actual rotation of the dial.

function updateRotation() {
  let newText = gsap.utils.mapRange(-135, 135, 0, 100, this.rotation)
  hslaData.innerText = newText.toFixed()
}

Once that change is made in the updateRotation() function, we have this result. See how when the dial is in the 12 o’clock position, we get 50? -135 now results in 0 and 135 gives us 100. We have successfully mapped the dial rotation to the desired range of 0 → 100. Pretty cool, huh?

See the Pen HSLA Dial Tutorial Single Dial Part 2 (mapRange) by Craig Roblewsky (@PointC) on CodePen.

Multiple dials

The saturation and lightness dials will work exactly like the single dial above so their work is pretty much done. Hue will range from 0 → 360 and alpha will range from 0 → 1 so we have to change their code a bit.

We don’t want to repeat ourselves and create four separate dials so we’ll use some variables and a loop.

const rotMin = -135;
const rotMax = 135;
const color = [180, 50, 50, 1]; // initial HSLA color
const upperRange = [360, 100, 100, 1]; //upper map range for each dial
const colorContainer = document.querySelector("#colorContainer");
const hslaData = document.querySelector("#hslaData");
const dials = gsap.utils.toArray(".dial");

The rotation variables (-135, 135) are the same values as above. The color array is the start color of the background element and has the same value as I’ve set in the CSS. The upperRange array is the upper limit of each value. We have the same text feedback element and we use GreenSock’s handy utils.toArray() method to select all four dials.

The Loop

Nothing too fancy here. Each dial is set to rotation and has the same bounds. An index is set for each so the updateColor() function knows which dial we turned. Finally, there is a check to see if it’s the alpha dial (index === 3) and if so, rotate it to max. This is because I want the initial alpha to be 1 instead of 0.5 so the dial is cranked up to 135° by default.

dials.forEach((obj, i) => {
  let dial = new Draggable(obj, {
    type: "rotation",
    bounds: {
      minRotation: rotMin,
      maxRotation: rotMax
    },
    onDrag: updateColor
  });
  dial.index = i;
  if (i === 3) {
    gsap.set(obj, { rotation: rotMax }); // alpha dial should initially be at 1
  }
});

The color function

There are a couple changes in the updateColor() function. We now get the index of the dial so we know which value we’re changing. The rounding is also based on that index. We don’t want decimals in our result unless it’s the alpha dial. The mapRange() is nearly the same as above with the single dial except we feed in the upperRange from the array based on the dial index.

function updateColor() {
  let i = this.index;
  let rot = this.rotation;
  let round = i === 3 && rot < rotMax && rot > rotMin ? 2 : 0;
  color[i] = gsap.utils
    .mapRange(rotMin, rotMax, 0, upperRange[i], rot)
    .toFixed(round);
  let newColor = `hsla(${color[0]}, ${color[1]}%, ${color[2]}%, ${color[3]})`;
  hslaData.innerText = colorContainer.style.backgroundColor = newColor;
}

From there, we set the new color by updating one of the color array values. Again, this depends on the dial index. The text box is also updated to the same new color value.

The result

Once those updates are added, we get four interactive dials to change the hsla value of the background element. Note that I’ve added a checkerboard pattern to the body element background so the alpha setting would be more obvious.

See the Pen HSLA Dials Tutorial Part 3 by Craig Roblewsky (@PointC) on CodePen.

Bonus Demo

When I was working on this tutorial, I had a question and asked Jack Doyle (founder of GreenSock and code guru) about it. Instead of answering the question, he created a different version of the demo. He loves little code challenges like this and it’s always interesting to see what he creates. I thought it would be cool to include his so you could see how different it was.

I’m not going to explain the details of how he did things. Suffice to say he added a little markup (classes) to the HTML and used a reusable function to create the dials. He also used the super cool GSAP .pipe() and .snap() methods in his version. Check it out.

See the Pen HSLA Dials v3 (Jack’s) by Craig Roblewsky (@PointC) on CodePen.

Final thoughts

If your brain works like Jack’s, you may prefer his style of coding this demo. If you like a straightforward simple loop, you may prefer the way I did it. Maybe you’ll create a hybrid of the two. The results are the same and the amount of code is close to the same in each, but the approaches are quite different.

That’s the beauty (or perhaps the curse) of GSAP and JavaScript in general. There are many paths to the same result. It’s always fun to see how other people approach a project. Take the route you prefer. I hope you learned a little something about the mapRange() method and had some fun. Until next time, keep your pixels movin’.

If you found this information useful, please help me get the word out to the interwebs. I appreciate it. You're awesome!

Published: September 15, 2020 Last Updated: September 15, 2020

You might dig these articles too

No algorithm. Just hand chosen artisanal links.