Friday, 19 July 2013

Building a Colour Picker With HTML5, JavaScript and JQuery

I have recently been teaching myself HTML5 and JavaScript and had an idea for a project in which I would need to use a colour picker. After a little research I soon discovered the new HTML5 input types, one of which is a colour picker. Great! One problem though, although it does work very well in Chrome, it is not supported in Firefox 22 or IE10.

Obviously I want to support both IE and Firefox users so I did some hunting for an existing colour picker I could use. I did not really like the look or feel of the first few I came across so I thought why not build my own. This should be a good exercise for learning some of the techniques used in HTML5 and JavaScript. I have used Microsoft Expression Studio on several occasions and have always liked the colour picker used here so decided I would base mine on this.

The Expression Studio colour picker gives the option of displaying the selected colour in various colour models. My first version is just going to focus on the RGB colour model. The RGB model consists of three values, Red, Green and Blue, that each range from 0 to 255.

RGB is the most commonly used colour model on the web and usually represented as a 6 digit hexadecimal string such as "#FFFF00". The two left-most digits represent the red channel, the two middle digits represents the green channel and the 2 digits on the right represent the blue channel. In the example provided we have the colour yellow, made from maximum red and green channels.

However, to implement our colour picker we also need to be aware of another colour model, HSV. HSV stands for hue, saturation and value and is an alternative colour model but one that can be visually represented well in a colour picker.

HSV is a cylindrical geometry, hue starts with red 0 degrees and passes through the three primary and three secondary colours. Yellow at 60 degrees, green at 120, cyan is 180, blue 240, magenta 300 and then we are back to red at 360 degrees. Saturation and value are both measured between 0 and 1. The colours defined in the hue all have a saturation and value of 1. As you introduce white to a colour along the hue the saturation is reduced, as you introduce black the value is reduced. Below is an image showing the HSV cylinder.

The expression studio colour picker has several ways to manipulate a colour. You can manipulate the hue, saturation and value components. You can enter a 6 digit hexadecimal RGB string and when the RGB model is selected (the only model available in the first version of my colour picker) you can manipulate the three colour channels individually, either by typing in an integer between 0 and 255 or by dragging a slider.

Here is a screen shot of the Expression Studio colour picker. I have annotated the image to show how the hue, saturation and value properties relate.

So that is the background, now lets take a look at how we can make this component. To build the hue, saturation and value selection part of the component I'm going to use four HTML5 canvas elements. Two for the hue, one to display the full hue range from 0 to 360 degrees and another for the slider that the user will be able to drag up and down the hue. The other two canvas elements will be used for the saturation and value, one to display the range of values and the other to display a small circle that indicates the selected value that the user will also be able to drag around.

To make the hue we are going to draw a rectangle on to our canvas and fill it with a gradient. First we will get a reference to the canvas element using document.getElementById, then next we need to get a reference to the canvas element's context by calling canvas.getContext('2d'). The context object contains all the methods we need for drawing to the canvas.

  1. var canvas = document.getElementById('hue');
  2. var context = canvas.getContext('2d');

Next we will create a gradient object by calling context.createLinearGradient(x,y,x1,y1) on our context, the parameters specified allow us to determine the direction of the gradient. Then we can add multiple colour stops by calling gradient.addColorStop(stop,color). With this we can specify the position of each colour we would like to see along the hue. As we have discussed there are six colours on our hue so we will position them equally apart on our gradient in the order red, yellow, green, cyan, blue, magenta and finally back to red. You position a colour by specifying a value between 0 and 1.

  1. var grd = context.createLinearGradient(canvas.width, 0, canvas.width, canvas.height);
  2. grd.addColorStop(0, '#FF0000');
  3. grd.addColorStop(0.167, '#FFFF00');
  4. grd.addColorStop(0.333, '#00FF00');
  5. grd.addColorStop(0.5, '#00FFFF');
  6. grd.addColorStop(0.667, '#0000FF');
  7. grd.addColorStop(0.833, '#FF00FF');
  8. grd.addColorStop(1, '#FF0000');

Finally we are going to draw our rectangle with context.rect(x,y,width,height), we will set the fillstyle to our gradient object, then we call context.fill() and there we have it. A few simple lines of code and we have drawn the hue.

  1. context.rect(0, 0, canvas.width, canvas.height);
  2. context.fillStyle = grd;
  3. context.fill();

Next we have the slider which is going to be used to select the value along the hue. This is our second canvas and will very simply consist of two solid black triangles. To draw triangles we are going to need some different methods of the context object. We are going to draw a path so to start with we need to call context.beginPath() which starts or resets a path. Next we call context.moveTo(x,y), this gives the starting position of our path. Then we can call context.lineTo(x,y) which adds a point to the path and draws a line between that and the previous point. Finally we will need to call context.closePath(), this will draw a line from the current point back to the starting point.

  1. context.fillStyle = '#000000';
  2. context.beginPath();
  3. context.moveTo(0, 0);
  4. context.lineTo(7, 8);
  5. context.lineTo(0, 15);
  6. context.closePath();
  7. context.fill();

To make our saturation and value selector on the third canvas we need a gradient that goes in two different directions. We need to start with the selected hue colour in the top right corner and fade to white for our saturation along the x axis, but then also we need to fade to black for the value along the y axis. To achieve this affect we first draw a solid rectangle of our selected hue colour. Next we draw two more rectangles that will both be filled using a gradient, however this time we are going to use the alpha channel. The alpha channel takes a value between 0 and 1 and allows us to specify the opacity. A value of 1 is fully opaque and 0 is transparent. So our gradients will go from fully opaque white (or black) to fully transparent white. When laid over the top of our selected hue colour this gives us the desired effect. To specify a colour with an alpha value we use the css notation like so.

  1. var grd = context.createLinearGradient(canvas.width, canvas.height, 0, canvas.height);
  2. grd.addColorStop(0, "rgba(255,255,255,0)");
  3. grd.addColorStop(1, "#FFFFFF");
  4. context.fillStyle = grd;
  5. context.fill();

For the final canvas used in the hue, saturation and value part of our component we just need to draw two circles, a black circle outlined with a grey circle. To do this we use context.arc(x,y,r,sAngle,eAngle,counterclockwise) which can draw a circle or part of a circle. This function takes the x and y coordinates of the center of the circle, the radius, the start angle and end angle (the counter clockwise parameter is optional).

The next step is to add some JavaScript to handle the user interactions with the hue, saturation and value properties of our colour picker. To do this we need to handle some mouse events, we will take a look at how to do this with the hue. When a user presses the left mouse button down over the hue canvas (or slider), we want that to register that click as a selection on the hue. If the users then moves the mouse up or down whilst still holding down the mouse button we want to drag the slider up or down thus changing the hue selection.

To achieve our goal we will handle the mousedown event of both the hue canvas and the slider canvas, this will take care of the user clicking in the desired region. However we will handle the mousemove, mouseup and mouseleave events on the document object. Events will bubble up from any element within the document allowing the user to easily select values without being constrained to the region of the hue canvas. Within the mousedown events we set a global boolean variable to true and position the slider so it centers on the location of the click, in the mouseleave and mouseup events we set the boolean variable to false and in the mousemove event if our boolean variable is true we position the slider based on the location of the mouse. The events handlers would look something like this.

  1. $('#hue').mousedown(function (event) {
  2.     hueChange = true;
  3.     PositionSlider(event);
  4. });
  6. $('#slider').mousedown(function (event) {
  7.     hueChange = true;
  8.     PositionSlider(event);
  9. });
  11. $(document).mouseup(function () {
  12.     hueChange = false;
  13. });
  15. $(document).mouseleave(function () {
  16.     hueChange = false;
  17. });
  19. $(document).mousemove(function (event) {
  20.     if (hueChange) {
  21.         PositionSlider(event);
  22.     }
  23. });

The position slider function takes the mouse location and calculates the position over the hue canvas. The event object gives us a pageY property, this tells us the y coordinate of the mouse. We then need to subtract the y coordinate of the location of the hue canvas. HTML elements have an offsetTop property that gives us this value however it is only relative to the parent element. To get the true 'top' value of an element we need to loop through the parent elements and sum up the offsetTop property for all of these. Here is the function I wrote to do this.

  1. function GetElementTop(element) {
  2.     var top = element.offsetTop;
  3.     var parent = element.offsetParent;
  5.     while (parent != undefined) {
  6.         top = top + parent.offsetTop;
  7.         parent = parent.offsetParent;
  8.     }
  10.     return top;
  11. }

The rest of the component is basically made up of a combination of the techniques described above. There is some maths in there to convert RGB colours to HSV and back again. There are plenty of very detailed articles out there that describe the maths so I will not do so here. A working version of my colour picker can be found on my icons editor project.

No comments:

Post a Comment