Enhancing the JavaScript Color Picker with the Canvas element
With the previous implementation of the Color Picker I used div elements to make the individual “pixels” of the color picker, attaching an event to each div element. This incurs quite a bit of overhead, causing the color picker to perform poorly especially on slower systems. The most obvious way to create something like a color picker efficiently is to use the Canvas element.
First, I needed to detect whether the browser supports the Canvas element. I do that by creating a Canvas element and then seeing if it will return a context like so:
testcanvas = document.createElement("canvas");
if(testcanvas.getContext){
this.supportsCanvas = true;
delete testelement;
}else{
this.supportsCanvas = false;
delete testelement;
}
Next, in the constructor I needed to create Canvas elements instead of div containers for the Hue Bar and SV Box. Here is a snip of code for the Hue bar:
if(this.supportsCanvas){
//create the canvas Saturation Value Box.
this.HueBar = new Element("canvas");
this.HueBar.setStyle("cssFloat", "left");
this.HueBar.setStyle("styleFloat", "left");
this.HueBar.setStyle("margin-right", "5px");
this.HueBar.width=30;
this.HueBar.height=360;
this.HueBar.addEvent("click", this.setCurrentHue.bind(this));
this.Container.appendChild(this.HueBar);
}else{
//create the container for the hue bar.
this.HueBar = new Element("div");
this.HueBar.setStyle("width", "30px");
this.HueBar.setStyle("cssFloat", "left");
this.HueBar.setStyle("styleFloat", "left");
this.HueBar.setStyle("margin-right", "5px");
this.Container.appendChild(this.HueBar);
}
Note: in the case of using the Canvas element, the events are attached to the canvas instead of child elements.
The next difference is in the drawHueBar and drawSVBox functions. In the drawHueBar function, we figure out the multiplier we will need for get steps between 0 and 360 for the height of our bar. Create a new color that is at hue 0 and full saturation and brightness. Get a 2d context to the HueBar canvas element. Iterate over the height of the HueBar. Set the hue for the current position on the HueBar. Get the Hex color string for the new Hue. Set the Canvas Context’s fill style to the Hex color string. Then, fill a Rect that is as wide as the HueBar and 1 pixel tall with that color. Rinse and repeat until we have iterated the whole HueBar.
Here is a snip of code from the drawHueBar function:
drawHueBar: function (){
if(this.supportsCanvas){
hSteps = 360 / this.HueBar.height;
hColor = new Color([0,100,100], 'hsb');
myCTX = this.HueBar.getContext('2d');
strhHex = "";
for(hi = this.HueBar.height; hi > 0; hi--){
hColor.changeHue(hi*hSteps);
strhHex = hColor.rgbToHex();
myCTX.fillStyle = strhHex;
myCTX.fillRect(0, this.HueBar.height-hi, this.HueBar.width, 1);
}
}else{
hsvColor = new Color([0,100,100], 'hsb');
fcoDiv = new Element("div");
fcoDiv.setStyle("width","30px");
fcoDiv.setStyle("height","1px");
...
}
}
For the SVBox, it is mostly the same, changing the Brightness in the loop instead of the Hue. I make use of the Canvas element’s gradient fill functions to simplify the work. Inside the loop over the height of the SVBox, we create a new gradient that goes from the left side of the SVBox to the Right. We add a color stop to the linear Gradient that is just a gray value between white and black based on the current position in the height of the SVBox. Then we add a color stop that is based on the current iterations HSV value. Set the Canvas context’s fill style to the gradient and then fill one row of the SVBox.
myLinearGrad = myCTX.createLinearGradient(0, 0, this.SVBox.width, 0);
myLinearGrad.addColorStop(0, "rgb("+vi+","+vi+","+vi+")");
myLinearGrad.addColorStop(1, strsvHex);
myCTX.fillStyle = myLinearGrad;
In the case of using the Canvas element, the UpdateSVBox code does the exact same thing as the drawSVBox.
Getting the color clicked on is a little different and we need to deal with the DOM a little more. In order to get the color clicked on we need to call the getImageData(X, Y) function from a Canvas context object. The issue with that is the event.ClientX and the event.ClientY that is passed in is in the window coordinate space, not our canvas elements coordinate space, so we have to translate. In the event call back functions we need to do the following to get the colors from the Canvas elements. Get a 2d context to the Canvas element. Call the MooTools Element.getCoordinates() function to get the left and top of the canvas in the Window coordinate space. Then, for each axis subtract the Canvases left or top from the clientX or clientY then add the window.scrollX or window.scrollY to deal with the page being scrolled if it is. Pass the resulting X and Y into the context’s getImageData function to get an imageData object for the pixel. The imageData.data array will contain the RGB values for the pixel.
myCTX = this.HueBar.getContext('2d');
hBoxCoords = this.HueBar.getCoordinates();
myImageData = myCTX.getImageData(e.clientX - hBoxCoords.left + window.scrollX, e.clientY - hBoxCoords.top + window.scrollY, 1, 1);
CurHueColor = new Color([myImageData.data[0], myImageData.data[1], myImageData.data[2]]);
With this update the Color Picker is significantly faster with a capital ‘S’, unless you are using IE and then it is just as slow as before. (A fact which totally burns my britches, since IE has such a large portion of the browser market share. I will have to figure out something to do about that in the IE case.)
See the code for the whole Canvas enhanced ColorPicker class Canvas based Color Picker exampleThis code was tested in IE 7 and Firefox 2. The Canvas element was not added to Firefox until version 2, no Canvas element in IE.





















[…] Enhancing the JavaScript Color Picker with the Canvas element […]
March 26th, 2007 at 7:58 am