Javascript - Interactive Color Picker

Skill

Javascript - Interactive Color Picker

Posted in:

Today, we are going to take a look at how to create an interactive color picker using javascript, css, and html. There are a number of color picker packages out there on the web, but there isn't nearly as much on how to write one yourself and the mechanics behind how it works. Hopefully, after reading this article, you would be able to go off and write one yourself.

This tutorial builds on a number of other javascript articles on this site, including Javascript Objects - A Useful Example (which covers some basics about how objects work in javascript - and we will be using the color object created in that tutorial), Javascript - Working With Events (which covers how to work with events and event objects), and Javascript - Draggable Elements (which covers how to do dragging in javascript). So, if you are relatively new to javascript, I would suggest at least skimming those before you continue.

Below, you can see the result of what we will create today. You can adjust the hue by dragging the arrows on the color bar, and can adjust saturation and value by dragging around the small circle in the gradient square. The hex, RGB, and HSV values are output in the textboxes on the right, as well as a div containing the color picked. You can also enter values in the hex, RGB and HSV text boxes, and the correct color will show up (and the arrows and circle will move to the correct position). What we are going to create today is not a package, but it could be made into one very easily. Play around a bit - I know you want to.



Hex:
Red:
Green:
Blue:
Hue:
Saturation:
Value:

Ok, now that you've had your fun, lets dive right in. Right below, we have the html required to create the color picker:

<div style="position:relative;height:286px;width:531px;border:1px solid black;">
  <div id="gradientBox" style="cursor:crosshair;top:15px;position:absolute;
                              left:15px;width:256px;height:256px;">

    <img id="gradientImg" style="display:block;width:256px;height:256px;"
        src="/Color_Picker/color_picker_gradient.png" />
    <img id="circle" style="position:absolute;height:11px;width:11px;"  
        src="/Color_Picker/color_picker_circle.gif" />
  </div>
  <div id="hueBarDiv" style="position:absolute;left:310px;width:35px;
                            height:256px;top:15px;">

    <img style="position:absolute;height:256px; width:19px;left:8px;"
        src="/Color_Picker/color_picker_bar.png" />
    <img id="arrows" style="position:absolute;height:9px;width:35px;left:0px;"
        src="/Color_Picker/color_picker_arrows.gif" />
  </div>
  <div style="position:absolute;left:370px;width:145px;height:256px;top:15px;">
  <div style="position:absolute;border: 1px solid black;
             height:50px;width:145px;top:0px;left:0px;">

    <div id="quickColor" style="position:absolute;height:50px;width:73px;
                               top:0px;left:0px;">

    </div>
    <div id="staticColor" style="position:absolute;height:50px;width:72px;
                                top:0px;left:73px;">

    </div>
  </div>
  <br />
  <table width="100%" style="position:absolute;top:55px;">
    <tr>
      <td>Hex: </td>
      <td>
        <input size="8" type="text" id="hexBox" onchange="hexBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Red: </td>
      <td>
        <input size="8" type="text" id="redBox" onchange="redBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Green: </td>
      <td>
        <input size="8" type="text" id="greenBox" onchange="greenBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Blue: </td>
      <td>
        <input size="8" type="text" id="blueBox" onchange="blueBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Hue: </td>
      <td>
        <input size="8" type="text" id="hueBox" onchange="hueBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Saturation: </td>
      <td>
        <input size="8" type="text" id="saturationBox"
           onchange="saturationBoxChanged();" />
      </td>
    </tr>
    <tr>
      <td>Value: </td>
      <td>
        <input size="8" type="text" id="valueBox" onchange="valueBoxChanged();" />
      </td>
    </tr>
  </table>
  </div>
</div>

The stuff in the bottom half is really simple - it is just a table for the text boxes and their labels. Of course, each of them has a javascript function attached to their onchange event - but we will go into what those function do later. For now, we want to take a look at the top part - the divs that define the gradient box and the hue bar. The gradient box is just a div with a 256x256 partially transparent image inside of it. This partially transparent image is what gives the gradient - the upper right corner is completely transparent, the bottom right is solid white, and the left is solid black. Setting the background color on this div creates the appearance of a saturation-value gradient in that background color. This div also contains the circle image that represents the selected point.

The hue bar doesn't require any gradient tricks - it is just a standard hue bar, where the bottom is a hue of 0 and the top is a hue of 359. The arrows are a single image sitting on top of the bar, where the center of the arrow image is transparent. The other divs represent the color box on the upper right of the color picker - it is split into two divs, one which represents the current color that you are dragging over (quickColor), and the other represents the last color that you picked (staticColor). When you finish a drag operation - either on the gradient or the bar - both divs will update to show the color that the drag operation completed on.

So now that you have an idea of what is where in terms of the html elements, lets turn to the javascript. Below, we have the initialization code for the color picker:

fixGradientImg();
var currentColor = Colors.ColorFromRGB(64,128,128);
new dragObject("arrows", "hueBarDiv", arrowsLowBounds, arrowsUpBounds,
               arrowsDown, arrowsMoved, endMovement);
new dragObject("circle", "gradientBox", circleLowBounds, circleUpBounds,
               circleDown, circleMoved, endMovement);
colorChanged('box');

Obviously, not much of that makes sense at the moment, but we shall take a look one line at a time. The first line, the call to fixGradientImage, is what makes this color picker work in Internet Explorer 6. IE6, as you probably know, does not support transparency on pngs by default - and so we have to apply this hack to get the gradient image to appear correctly. The function looks like the following:

function fixGradientImg()
{
  fixPNG(document.getElementById("gradientImg"));
}

function fixPNG(myImage)
{
  if(!document.body.filters)
    return;
  var arVersion = navigator.appVersion.split("MSIE");
  var version = parseFloat(arVersion[1]);
  if(version < 5.5 || version >= 7)
    return;

  var imgID = (myImage.id) ? "id='" + myImage.id + "' " : ""
  var imgStyle = "display:inline-block;" + myImage.style.cssText
  var strNewHTML = "<span " + imgID + " style=\"width:" + myImage.width
              + "px; height:" + myImage.height + "px;" + imgStyle + ";"
              + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader"
              + "(src=\'" + myImage.src + "\', sizingMethod='scale');\"></span>"
  myImage.outerHTML = strNewHTML
}

There is plenty of info on the web about what exactly this hack does in IE6, so I won't go over that here. Suffice it to say, where we had a opaque image, we now have a span with a partially transparent png.

The next line of initialization code (var currentColor = Colors.ColorFromRGB(64,128,128);) sets up the global object that will always hold the currently selected color, and initializes it to a value smack dab in the middle of the spectrum. This is probably a good time to re-introduce the color object from Javascript Objects - A Useful Example. And here it is in all its glory:

var Colors = new function()
{
  this.ColorFromHSV = function(hue, sat, val)
  {
    var color = new Color();
    color.SetHSV(hue,sat,val);
    return color;
  }

  this.ColorFromRGB = function(r, g, b)
  {
    var color = new Color();
    color.SetRGB(r,g,b);
    return color;
  }

  this.ColorFromHex = function(hexStr)
  {
    var color = new Color();
    color.SetHexString(hexStr);
    return color;
  }

  function Color()
  {
    //Stored as values between 0 and 1
    var red = 0;
    var green = 0;
    var blue = 0;
   
    //Stored as values between 0 and 360
    var hue = 0;
   
    //Strored as values between 0 and 1

    var saturation = 0;
    var value = 0;
     
    this.SetRGB = function(r, g, b)
    {
      if (isNaN(r) || isNaN(g) || isNaN(b))
        return false;
       
      r = r/255.0;
      red = r > 1 ? 1 : r < 0 ? 0 : r;
      g = g/255.0;
      green = g > 1 ? 1 : g < 0 ? 0 : g;
      b = b/255.0;
      blue = b > 1 ? 1 : b < 0 ? 0 : b;
     
      calculateHSV();
      return true;
    }
   
    this.Red = function()
    { return Math.round(red*255); }
   
    this.Green = function()
    { return Math.round(green*255); }
   
    this.Blue = function()
    { return Math.round(blue*255); }
   
    this.SetHSV = function(h, s, v)
    {
      if (isNaN(h) || isNaN(s) || isNaN(v))
        return false;
       
      hue = (h >= 360) ? 359.99 : (h < 0) ? 0 : h;
      saturation = (s > 1) ? 1 : (s < 0) ? 0 : s;
      value = (v > 1) ? 1 : (v < 0) ? 0 : v;
      calculateRGB();
      return true;
    }
     
    this.Hue = function()
    { return hue; }
     
    this.Saturation = function()
    { return saturation; }
     
    this.Value = function()
    { return value; }
     
    this.SetHexString = function(hexString)
    {
      if(hexString == null || typeof(hexString) != "string")
        return false;
       
      if (hexString.substr(0, 1) == '#')
        hexString = hexString.substr(1);
       
      if(hexString.length != 6)
        return false;
         
      var r = parseInt(hexString.substr(0, 2), 16);
      var g = parseInt(hexString.substr(2, 2), 16);
      var b = parseInt(hexString.substr(4, 2), 16);
     
      return this.SetRGB(r,g,b);
    }
     
    this.HexString = function()
    {
      var rStr = this.Red().toString(16);
      if (rStr.length == 1)
        rStr = '0' + rStr;
      var gStr = this.Green().toString(16);
      if (gStr.length == 1)
        gStr = '0' + gStr;
      var bStr = this.Blue().toString(16);
      if (bStr.length == 1)
        bStr = '0' + bStr;
      return ('#' + rStr + gStr + bStr).toUpperCase();
    }
   
    this.Complement = function()
    {
      var newHue = (hue>= 180) ? hue - 180 : hue + 180;
      var newVal = (value * (saturation - 1) + 1);
      var newSat = (value*saturation) / newVal;
      var newColor = new Color();
      newColor.SetHSV(newHue, newSat, newVal);
      return newColor;
    }
   
    function calculateHSV()
    {
      var max = Math.max(Math.max(red, green), blue);
      var min = Math.min(Math.min(red, green), blue);
     
      value = max;
     
      saturation = 0;
      if(max != 0)
        saturation = 1 - min/max;
       
      hue = 0;
      if(min == max)
        return;
     
      var delta = (max - min);
      if (red == max)
        hue = (green - blue) / delta;
      else if (green == max)
        hue = 2 + ((blue - red) / delta);
      else
        hue = 4 + ((red - green) / delta);
      hue = hue * 60;
      if(hue <0)
        hue += 360;
    }
   
    function calculateRGB()
    {
      red = value;
      green = value;
      blue = value;
     
      if(value == 0 || saturation == 0)
        return;
     
      var tHue = (hue / 60);
      var i = Math.floor(tHue);
      var f = tHue - i;
      var p = value * (1 - saturation);
      var q = value * (1 - saturation * f);
      var t = value * (1 - saturation * (1 - f));
      switch(i)
      {
        case 0:
          red = value; green = t; blue = p;
          break;
        case 1:
          red = q; green = value; blue = p;
          break;
        case 2:
          red = p; green = value; blue = t;
          break;
        case 3:
          red = p; green = q; blue = value;
          break;
        case 4:
          red = t; green = p; blue = value;
          break;
        default:
          red = value; green = p; blue = q;
          break;
      }
    }
  }
}
();

This code, as explained in that previous tutorial, creates a static Colors object which can be used to create color objects (which can take RGB, HSV, or Hex and produce RGB, HSV, or Hex). I'm not going to go into detail on how it works here - if you want to know more about it, read the tutorial.

The next two lines in the initialization hook the two elements that need to be draggable - the circle and the arrows.

new dragObject("arrows", "hueBarDiv", arrowsLowBounds, arrowsUpBounds,
               arrowsDown, arrowsMoved, endMovement);
new dragObject("circle", "gradientBox", circleLowBounds, circleUpBounds,
               circleDown, circleMoved, endMovement);

The first line here declares that the element arrows is draggable and hueBarDiv is the handle. This means that if you click anywhere on the hueBarDiv, the arrows will start dragging. Then we give it a lower and an upper position bound for where we can drag the arrows, and three functions, a function to call at the beginning of dragging, one after each movement, and one when dragging completes. We set the same type of thing up in the second line, for the circle on the gradient image - but in this case the gradientBox is the drag handle - so if you click anywhere on the gradient image, you start dragging the circle.

Just as a refresher, here is the main chunk of code behind dragObj:

function dragObject(element, attachElement, lowerBound, upperBound,
                    startCallback, moveCallback, endCallback, attachLater)
{
  if(typeof(element) == "string")
    element = document.getElementById(element);
  if(element == null)
      return;
 
  if(lowerBound != null && upperBound != null)
  {
    var temp = lowerBound.Min(upperBound);
    upperBound = lowerBound.Max(upperBound);
    lowerBound = temp;
  }

  var cursorStartPos = null;
  var elementStartPos = null;
  var dragging = false;
  var listening = false;
  var disposed = false;
 
  function dragStart(eventObj)
  {
    if(dragging || !listening || disposed)
      return;

    dragging = true;
   
    if(startCallback != null)
      startCallback(eventObj, element);
   
    cursorStartPos = absoluteCursorPostion(eventObj);
   
    elementStartPos = new Position(parseInt(element.style.left),
                                   parseInt(element.style.top));
   
    elementStartPos = elementStartPos.Check();
   
    hookEvent(document, "mousemove", dragGo);
    hookEvent(document, "mouseup", dragStopHook);
   
    return cancelEvent(eventObj);
  }
 
  function dragGo(eventObj)
  {
    if(!dragging || disposed)
      return;
   
    var newPos = absoluteCursorPostion(eventObj);
    newPos = newPos.Add(elementStartPos)
    newPos = newPos.Subtract(cursorStartPos);
    newPos = newPos.Bound(lowerBound, upperBound)
    newPos.Apply(element);
    if(moveCallback != null)
      moveCallback(newPos, element);
       
    return cancelEvent(eventObj);
  }
 
  function dragStopHook(eventObj)
  {
    dragStop();
    return cancelEvent(eventObj);
  }
 
  function dragStop()
  {
    if(!dragging || disposed)
      return;

    unhookEvent(document, "mousemove", dragGo);
    unhookEvent(document, "mouseup", dragStopHook);
    cursorStartPos = null;
    elementStartPos = null;
    if(endCallback != null)
      endCallback(element);
    dragging = false;
  }
 
  this.Dispose = function()
  {
    if(disposed)
      return;


    this.StopListening(true);
    element = null;
    attachElement = null
    lowerBound = null;
    upperBound = null;
    startCallback = null;
    moveCallback = null
    endCallback = null;
    disposed = true;
  }
 
  this.StartListening = function()
  {
    if(listening || disposed)
      return;

    listening = true;
    hookEvent(attachElement, "mousedown", dragStart);
  }
 
  this.StopListening = function(stopCurrentDragging)
  {
    if(!listening || disposed)
      return;

    unhookEvent(attachElement, "mousedown", dragStart);
    listening = false;
   
    if(stopCurrentDragging && dragging)
      dragStop();
  }
 
  this.IsDragging = function(){ return dragging; }
  this.IsListening = function() { return listening; }
  this.IsDisposed = function() { return disposed; }
 
  if(typeof(attachElement) == "string")
    attachElement = document.getElementById(attachElement);
  if(attachElement == null)
    attachElement = element;
   
  if(!attachLater)
    this.StartListening();
}

This code is from the Javascript - Draggable Elements tutorial. Again, since there is a very detailed discussion of the draggable code in that tutorial, I'm just going to gloss right over it here.

So back to those drag object initializations - what are the upper and lower bounds of the arrows and the circle? They are position objects (and if you don't know what our position object is, go read the draggable elements tutorial again). And here is what they are defined as:

var arrowsLowBounds = new Position(0, -4);
var arrowsUpBounds = new Position(0, 251);
var circleLowBounds = new Position(-5, -5);
var circleUpBounds = new Position(250, 250);

The fact that both the lower and the upper X value for the arrows is the same number means that it can't be dragged left or right, but only up or down. The reason the other numbers aren't quite what you might expect is that these positions represent the upper left corner of the circle/arrow elements. This means that they are offset by the half the width and half the height so that the center of the elements can reach the edge of its bounding area.

So those are the bounds - now what about the callbacks? Well, first we have the start callbacks - circleDown and arrowsDown. Lets see what they do:

function arrowsDown(e, arrows)
{
  var pos = getMousePos(e);
 
  if(getEventTarget(e) == arrows)
    pos.Y += parseInt(arrows.style.top);
 
  pos = correctOffset(pos, arrowsOffset, true);
 
  pos = pos.Bound(arrowsLowBounds, arrowsUpBounds);
 
  pos.Apply(arrows);
 
  arrowsMoved(pos);
}

function circleDown(e, circle)
{
  var pos = getMousePos(e);
 
  if(getEventTarget(e) == circle)
  {
    pos.X += parseInt(circle.style.left);
    pos.Y += parseInt(circle.style.top);
  }
 
  pos = correctOffset(pos, circleOffset, true);
 
  pos = pos.Bound(circleLowBounds, circleUpBounds);
 
  pos.Apply(circle);
   
  circleMoved(pos);
}

These two functions may be small but they accomplish a lot. Lets go over the arrowsDown one step by step. First we get the current mouse position from the event object. This is done with the following function:

function getMousePos(eventObj)
{
  eventObj = eventObj ? eventObj : window.event;
  var pos;
  if(isNaN(eventObj.layerX))
    pos = new Position(eventObj.offsetX, eventObj.offsetY);
  else
    pos = new Position(eventObj.layerX, eventObj.layerY);
  return correctOffset(pos, pointerOffset, true);
}

Here we get the correct event object (see Javascript - Working With Events for an explanation of why), and then we get the current mouse position, relative to the element that triggered the event. Of course, Internet Explorer and Firefox do this differently, so we have the two different ways of getting the number here. Once we have that position we call the function correctOffset, and get a new position back (which we then return). This correctOffset function is rather simplistic, but serves an important purpose:

function correctOffset(pos, offset, neg)
{
  if(neg)
    return pos.Subtract(offset);
  return pos.Add(offset);
}

All it does is take a position and an offset (both position objects), and either applies or unapplies that offset (according to the third argument). We use it in the getMousePos function with the pointerOffset:

var pointerOffset = new Position(0, navigator.userAgent.indexOf("Firefox") >= 0 ? 1 : 0);
var circleOffset = new Position(5, 5);
var arrowsOffset = new Position(0, 4);

For whatever horrible reason, where Firefox reports the tip of the pointer to be is not actually the tip of the pointer - it is 1 pixel off in the X direction. So, we need this pointer offset value. The other two offsets listed here are much more acceptable - they are the values to get from the upper left pixel of the circle/arrow to the center point value that we need (we will be using these two offsets a bunch later).

Ok, so back to the arrowsDown code. We now have the current mouse position relative to the element that triggered the event. The next thing we do is check what element triggered the event - and if it is the arrows themselves, we add the current position of the arrows (in the Y direction) to the Y mouse position. This way we now have the mouse position relative to the hue bar. The reason we don't do anything with the X position here is that the X position of the arrows never changes. Now we call correctOffset again, but this time it is to adjust for the height of the arrows image (i.e., we adjust it using the arrowsOffset). This is so that the current mouse position ends up at the center of the arrows image, instead of at the top. Now that we have what will become the new position for the arrows, we bound it to make sure it falls within the correct range (using the lower and upper arrow bounds). Then we apply the new position to the arrows element. Finally, we call the function arrowsMoved with the new position. What all this code essentially accomplishes is to move the arrows to the point at which you clicked on the hue bar. The code for circleDown accomplishes the exact same task, but for the circle on the gradient image.

Now on to the functions arrowsMoved and circleMoved. They are called at the end of their respective 'Down' functions, and they are also called after every movement during a drag. So lets see what they do:

function arrowsMoved(pos, element)
{
  pos = correctOffset(pos, arrowsOffset, false);
  currentColor.SetHSV((256 - pos.Y)*359.99/255, currentColor.Saturation(),
      currentColor.Value());
  colorChanged("arrows");
}

function circleMoved(pos, element)
{
  pos = correctOffset(pos, circleOffset, false);
  currentColor.SetHSV(currentColor.Hue(), 1-pos.Y/255.0, pos.X/255.0);
  colorChanged("circle");
}

Again, the two functions look extremely similar. They start by offsetting the position (the position passed in is the one for the new upper-left position of the element, so we are bringing it back to the center of the their respective elements). Next we set the new current color, based on the position. For the arrows, the Y position transforms into the hue - first we flip it (since in coordinate space, 0 is at the top, but in the space of the hue bar, 0 is at the bottom), and then we scale it from the range 0-255 (the range of the hue bar image) to the range 0-359.99 (the actual range of hue that we deal with). For the circle, the Y position becomes the saturation (where again, it is flipped) and we scale it from the range 0-255 to the range 0-1. The X position becomes the new value, and here we just scale it from 0-255 to 0-1. Finally, we call a function called colorChanged:

function colorChanged(source)
{
  document.getElementById("hexBox").value = currentColor.HexString();
  document.getElementById("redBox").value = currentColor.Red();
  document.getElementById("greenBox").value = currentColor.Green();
  document.getElementById("blueBox").value = currentColor.Blue();
  document.getElementById("hueBox").value = Math.round(currentColor.Hue());
 
  var str = (currentColor.Saturation()*100).toString();
  if(str.length > 4)
    str = str.substr(0,4);
  document.getElementById("saturationBox").value = str;
 
  str = (currentColor.Value()*100).toString();
  if(str.length > 4)
    str = str.substr(0,4);
  document.getElementById("valueBox").value = str;
 
  if(source == "arrows" || source == "box")
    document.getElementById("gradientBox").style.backgroundColor=
        Colors.ColorFromHSV(currentColor.Hue(), 1, 1).HexString();
   
  if(source == "box")
  {
    var el = document.getElementById("arrows");
    el.style.top = (256 - currentColor.Hue()*255/359.99 - arrowsOffset.Y) + 'px';
    var pos = new Position(currentColor.Value()*255,
                           (1-currentColor.Saturation())*255);
    pos = correctOffset(pos, circleOffset, true);
    pos.Apply("circle");
    endMovement();
  }
 
  document.getElementById("quickColor").style.backgroundColor =
      currentColor.HexString();
}

This function is where we synchronize all the various elements of the color picker. The one argument, source is a string representing where the new values have come from. This string has three possible values - "arrows", "circle", and "box" - where "arrows" means the position of the arrows has changes, "circle" means the position of the circle has changed, and "box" means that one of the input boxes has changed.

So first in this function, we update the various text boxes - because these always need to be updated, no matter what changed. The saturation and value text boxes get multiplied up to be a percentage and then rounded to look nicer. If the source was the arrows or the box, we update the background color of the gradient div - whose color we get by taking the current hue and giving it a value and saturation of 1. If the source was a text box, we update the position of the arrows and the circle, from the current color. Essentially, we do the reverse of the calculation that we used to get from position to color. We also call a function endMovement in this case, which I will explain in a moment. Finally, we update the current color of the quickColor div to be the currently selected color.

So now we have the function endMovement, which is called after a drag operation completes on the arrows or circle, and is also called whenever a text box causes something to change. It is pretty simple, all it does is set the staticColor div to the new color:

function endMovement()
{
  document.getElementById("staticColor").style.backgroundColor =
      currentColor.HexString();
}

We are actually almost done now! The final call in initialization was to call colorChanged('box');, which you understand now is to get all the color picker components synced with the initial color choice. There are only a couple more functions - and they are all extremely simple. They are the onchange functions for each of the text boxes:

function hexBoxChanged(e)
{
  currentColor.SetHexString(document.getElementById("hexBox").value);
  colorChanged("box");
}

function redBoxChanged(e)
{
  currentColor.SetRGB(parseInt(document.getElementById("redBox").value),
      currentColor.Green(), currentColor.Blue());
  colorChanged("box");
}

function greenBoxChanged(e)
{
  currentColor.SetRGB(currentColor.Red(),
      parseInt(document.getElementById("greenBox").value), currentColor.Blue());
  colorChanged("box");
}

function blueBoxChanged(e)
{
  currentColor.SetRGB(currentColor.Red(), currentColor.Green(),
      parseInt(document.getElementById("blueBox").value));
  colorChanged("box");
}

function hueBoxChanged(e)
{
  currentColor.SetHSV(parseFloat(document.getElementById("hueBox").value),
      currentColor.Saturation(), currentColor.Value());
  colorChanged("box");
}

function saturationBoxChanged(e)
{
  currentColor.SetHSV(currentColor.Hue(),
      parseFloat(document.getElementById("saturationBox").value)/100.0,
      currentColor.Value());
  colorChanged("box");
}

function valueBoxChanged(e)
{
  currentColor.SetHSV(currentColor.Hue(), currentColor.Saturation(),
      parseFloat(document.getElementById("valueBox").value)/100.0);
  colorChanged("box");
}

Each of those functions just takes the new value from its repective text box, pushes that new value into the current color object, and then calls colorChanged("box") in order to update everyone else.

And that covers it for how to make a color picker using javascript! Hope you enjoyed it, and if you would like the raw javascript code to play around with, feel free to download it here. The html and couple lines of initialization javascript are not in that file, but you can copy all that from the boxes above or the source of this page. As always, if you have any questions, please leave them in the comments.

George Panagopoulos
08/24/2007 - 08:41

could you please give links to the .png and .gif files that are in your code?

reply

The Tallest
08/24/2007 - 20:59

Sure! Here are all the links:
Gradient, Hue Bar, Arrows, Circle

reply

Orestis
12/09/2007 - 16:10

Is it possible, via javascript, to rotate the hue of an entire image, dynamicly? the ideal would be to provide a color pallete or a slider and according to the values(of the slider) that the user provides, the hue/saturation/brightness of the entire image, to change accordingly. if you know how to do so a tutorial would be just about great! :D

reply

Oasis
01/28/2008 - 14:01

I play with this code but the drag object did not work. Do I miss something?

reply

Iamkingarn
05/27/2008 - 23:18

I second oasis' motion. The drag object is not working... Have I done something wrong?

reply

The Tallest
05/28/2008 - 06:19

Perhaps you forgot to include the chunk on initialization javascript at the end. Here is what the full html code should look like:

<html>
  <head>
    <script src="javascript.js" type="text/javascript"></script>
  </head>
  <body>
    <div style="position:relative;height:286px;
        width:531px;border:1px solid black;">

      <div id="gradientBox" style="cursor:crosshair;
        top:15px;position:absolute;left:15px;
        width:256px;height:256px;">

        <img id="gradientImg"
        style="display:block;width:256px;height:256px;"
        src="/Color_Picker/color_picker_gradient.png" />
        <img id="circle"
        style="position:absolute;height:11px;
        width:11px;"

        src="/Color_Picker/color_picker_circle.gif" />
      </div>
      <div id="hueBarDiv" style="position:absolute;
       left:310px;width:35px;
       height:256px;top:15px;">

        <img style="position:absolute;height:256px;
         width:19px;left:8px;"

         src="/Color_Picker/color_picker_bar.png" />
        <img id="arrows" style="position:absolute;
         height:9px;width:35px;left:0px;"

         src="/Color_Picker/color_picker_arrows.gif" />
      </div>
      <div style="position:absolute;left:370px;
           width:145px;
           height:256px;top:15px;">

      <div style="position:absolute;
         border: 1px solid black;
         height:50px;width:145px;top:0px;left:0px;">

        <div id="quickColor" style="position:absolute;
           height:50px;width:73px;top:0px;left:0px;">

        </div>
        <div id="staticColor" style="position:absolute;
           height:50px;width:72px;top:0px;left:73px;">

        </div>
      </div>
      <br />
      <table width="100%"
         style="position:absolute;top:55px;">
        <tr>
          <td>Hex: </td>
          <td>
            <input size="8" type="text" id="hexBox"
               onchange="hexBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Red: </td>
          <td>
            <input size="8" type="text" id="redBox"
               onchange="redBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Green: </td>
          <td>
            <input size="8" type="text" id="greenBox"
               onchange="greenBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Blue: </td>
          <td>
            <input size="8" type="text" id="blueBox"
               onchange="blueBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Hue: </td>
          <td>
            <input size="8" type="text" id="hueBox"
               onchange="hueBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Saturation: </td>
          <td>
            <input size="8" type="text"
               id="saturationBox"
               onchange="saturationBoxChanged();" />
          </td>
        </tr>
        <tr>
          <td>Value: </td>
          <td>
            <input size="8" type="text" id="valueBox"
               onchange="valueBoxChanged();" />
          </td>
        </tr>
      </table>
      </div>
    </div>
    <script type="text/javascript">
      fixGradientImg();
      var currentColor = Colors.ColorFromRGB(64,128,128);
      new dragObject("arrows", "hueBarDiv",
          arrowsLowBounds, arrowsUpBounds,
          arrowsDown, arrowsMoved, endMovement);
      new dragObject("circle", "gradientBox",
          circleLowBounds, circleUpBounds,
          circleDown, circleMoved, endMovement);
      colorChanged('box');
    </script>
  <body>
</html>

reply

Walidator
02/09/2008 - 06:34

Here's an enhanced version of this color picker:
http://walidator.info/?s=colorpicker_en

reply

Kniffler
05/31/2008 - 17:16

I also had a bizarre problem with this. It turns out that the link to the javascript *must* be like

not like

Anyway, it's a great script. Thanks ferret.

reply

Kniffler
05/31/2008 - 17:17

Um, that is, like
<script src=“javascript.js” type=“text/javascript”></script>

not

<script src=“javascript.js” type=“text/javascript”/>

reply

The Reddest
06/01/2008 - 11:12

Kniffler, I've done that by accident way too many times. Also, you can refer to this post on how to embed code into any comments.

reply

Eldz
06/25/2008 - 07:26

Hi. I'd just ask for some advise on how I could modify this script to reduce the size of the color picker and still get the correct values? I've tried limiting the bounds during initialization and resized the image. However, it doesn't provide the correct values. Which part of the script should I modify? Thanks a lot!

reply

The Tallest
06/25/2008 - 08:01

Ok, it has been a while since I looked at this code, but I'm pretty sure you will need to change some numbers in three different functions: arrowsMoved, circleMoved, and colorChanged.

Essentially, you want to replace references to 256 with your new size, and references to 255 with your size minus 1.

Oh, and you'll want to change the bounds on the arrows and circle to your new numbers. So this code:

var arrowsLowBounds = new Position(0, -4);
var arrowsUpBounds = new Position(0, 251);
var circleLowBounds = new Position(-5, -5);
var circleUpBounds = new Position(250, 250);

Should become this:
var arrowsLowBounds = new Position(0, -4);
var arrowsUpBounds = new Position(0, NEWSIZE-5);
var circleLowBounds = new Position(-5, -5);
var circleUpBounds = new Position(NEWSIZE-6, NEWSIZE-6);

In retrospect, I should have pulled the size out as a constant.

reply

Вован
07/12/2008 - 00:15

+1 и ниипет!

reply

Koushik Ghosh
07/22/2008 - 23:29

I can not able to concatenate the code properly...so it give so many errors...can any one send me the complete code structure in 1/2 file(s) plz.....

Thank you

reply

Ray
08/05/2008 - 11:34

Thank you so much for this tutorial. I will plug-in this to a project I am working on. It works will with asp.net and it does not conflict with AJAX for .net.

thx a million
-Ray

reply

Ole
08/29/2008 - 11:29

It works on my nokia e70!!! -)

reply

Christoffer
09/19/2008 - 04:20

This is a great script, thanks!

Is it ok to use the code found here on a commercial site also as long as I modify the design a little?

reply

The Tallest
09/19/2008 - 07:38

Sure, go ahead, use it for whatever you would like. That's why it is out here :)

reply

Christoffer
10/10/2008 - 00:51

Excellent! Thanks for a good site, we are going to use this colorpicker for users to control the design of their own templates! Again, many thanks!

reply

CD Efasnu
10/26/2008 - 19:34

Thanks for this excellent tutorial and script!

I included the color picker into a Greasemonkey script that highlights movie links on IMDb.com:
http://userscripts.org/scripts/show/35145

reply

Саша
10/28/2008 - 00:47

Спасибо, отличный скрипт!!!
Thank you, excelent script!! =)

reply

Biya
12/09/2008 - 00:44

Hi
The code is very useful.
But the images are not displaying.
Can you please send me the full code with images.

Thanking you.

reply

Josh
03/31/2009 - 06:56

awesome code! thanks for sharing

reply

iisai
05/18/2009 - 19:56

Great script, and amazing tutorial(s)! Thank you so much!

reply

Anonymous
08/06/2009 - 13:16

You are awesome!!!!! I had another color picker but it was way too many files to download and upload to the server for my taste and your code rocks!!!!!!

reply

Arindam
10/24/2009 - 13:02

Excellent script(s) !! I am new to JavaScript - had a question - how do I set the selected color in the opener page ? On the page I have a "color picker" graphic next to a textbox that opens up the color picker in a pop-up window. I have added a button just under the color picker (in the popup) that says "Set Color". I would like to add code to the "onclick" event of the button and be able to set the selected color in the text box.

Any help will be greatly appreciated.

reply

Arindam
10/24/2009 - 16:24

Nevermind, figured it out.

reply

chrisrob
12/08/2009 - 10:54

Hi "The Tallest".

It's appropriate that your icon is Father Xmas because this script is an absolute gift.

Thanks and well done.

reply

hankjmatt
04/06/2010 - 21:12

I will plug-in this to a project I am working on. It works will with asp.net and it does not conflict with AJAX for .net.
club penguin cheats

reply

Dave
04/22/2010 - 10:15

Thanks for this fantastic piece of code.

reply

Feck McConnell
05/03/2010 - 16:11

Can you think of any reason this wouldn't work inside a 'lightbox' style jquery page? Possible variable name conflicts? anything like that?

I have a modular lightbox setup to run through a series of pages and render them in a lightbox type div. This code works fine outside jquery, but inside of it, the dragObject doesn't seem to get instantiated.

reply

Feck McConnell
05/03/2010 - 21:54

Yea, just noticed the author hasn't been here in around 2 years.Good luck Feck!

reply

amko_sa
05/18/2010 - 15:32

Thanks man i make this color picker on my site

reply

pixiestick
05/22/2010 - 04:30

@amko_sa... (or any expert) can you make this for my site? which is powered by microsoft... it is more like a blog spot. Pls quote your fee. pls email to nahoku44@gmail.com thank you.

reply

SalmanAbbas007
08/27/2010 - 10:19

haha thanks but i will go with YUI http://developer.yahoo.com/yui/examples/colorpicker/colorpicker-fromscript.html ;)

reply

BobIAm
09/29/2010 - 05:55

Hey how do you tell which color the user has chosen? I mean how does it read from circle in the image? O_o

reply

The Reddest
09/29/2010 - 08:46

Check out the functions arrowsMoved and circleMoved. The vertical slider represents the hue and the large gradient square represents the saturation. Since we know the size of these elements the values can be calculated based on the position of the mouse. If we know the hue and the saturation, a color can be derived.

reply

Anonymous
01/17/2011 - 15:19

arrowsMoved and circleMoved have an extra, unused argument: element.

reply

reblerebel
03/27/2011 - 12:08

ive tried adding this to HTML 5 and is doesn't work what can i do to fix this?

reply

ThangPT
09/26/2011 - 22:38

thank you so much for this tutorial, it's really great script for me

reply

Frank
11/09/2011 - 04:43

Hi,

Getting it up and running was easy. But I would need the hex codes without the # sign, or a coupled module won't work. I found this function:

this.HexString = function()
    {
      var rStr = this.Red().toString(16);
      if (rStr.length == 1)
        rStr = '0' + rStr;
      var gStr = this.Green().toString(16);
      if (gStr.length == 1)
        gStr = '0' + gStr;
      var bStr = this.Blue().toString(16);
      if (bStr.length == 1)
        bStr = '0' + bStr;
      return ('#' + rStr + gStr + bStr).toUpperCase();
    }

but if I remove the '#', the color picker doesn't work anymore in most browsers, only in IE. Can this still be done?

reply

Frank
11/09/2011 - 04:50

PS: to avoid mistakes: I only need the hex code to be without # sign in the form field. Whatever the color picker does internally is not important to me. In my setting, the hex code must be copied and pasted into a form field of external module. A module that I am neither able nor allowed to change.

reply

imagemoz
12/26/2011 - 13:04

Thanks for this tutorial, work fine on my site!
http://www.imagemoz.com/rgb/

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.