Copyright 2005-2006 David Betz
Updated September 2006
Samples (primarily for Firefox, but IE support was added)
A few years ago I found an article which stated that there are three things that JavaScript cannot do. The article listed these three as local file actions, networking, and graphics. How true is this? First off, it's very true that JavaScript can't access local files. I think we can all agree that this is a feature. I mean, do you really want to go to a website and have that website access your local files? Copying them to their own machines? Writing out new files? Copying viruses locally? If you want to be able to do stuff like that then you can use Internet Explorer. It hosts a technology known as ActiveX which allows just these things. Everybody I know has learned this the hard way. JavaScript on the other hand is safe and clean with regard to viruses. But, can JavaScript do networking? It really just depends on what you are trying to do. If you're trying to access a resource across over a local network, you're out of luck, however if you would like to access resources on a web server, JavaScript can easily do this via XmlHttp. Furthermore, in modern web browsers, JavaScript has the ability to work directly with SOAP messaging and web services (though many times as a wrapper of XmlHttp). So, yes it can, but only in incredibly safe ways (which is a plus).
Lastly, what about graphics development? While many people have always thought it to be impossible, it's actually been possible for many years. This is the topic I would like to discuss here.
The basis for all graphics development is basically the pixel. It's the simplest and most foundational unit of graphics development. With the ability to create pixels, the sky (or, rather, web browser performance) is the limit for graphics development. But, JavaScript can't write pixels, right? Sure it can. The trick behind JavaScript pixels is the use of standards XHTML objects. In our case we are going to use a very small colored div object to create a pixel. That's really all there is to it. To create more advanced graphics, you simply create more div objects. The beauty behind using a div object is that JavaScript can seamlessly interact with them and they are easy to place using simple 2D coordinates. In addition, div objects are easily customizable via CSS.
Using a div is great, but what size and shape should the div be exactly? You would think that it?s a 1px by 1px div object with a colored background and no border, but that?s won?t work since the buggy IE6 for Windows can't handle 1px by 1px div objects; it actually renders them as being vertical bars. To get around this problem our pixels will be created from transparent div objects with just about any height and a width of 1px and a colored border on the top only. This div object renders as a simple pixel and allows both Mozilla Firefox and IE to render pixels flawlessly.
I've stated at the outset that the prerequisites for this is understanding of the DOM, understanding of basic CSS, and understanding of modern web development or design paradigms. This is because all graphics development here relies heavily on the DOM, uses basics CSS as well as modern web development and design paradigms. If you are living in the old paradigms of presentation HTML, then I highly suggest you study up on XHTML/CSS development and design as well as shedding your old philosophies before continuing. Understanding of the DOM is also critically important for any modern web developer, since it?s via the DOM that you create new objects. This is what I'm going to demonstrate first.
To create a pixel you simply have to create the previously described div and put it somewhere.
function PlotPixel(x, y, c) {
var pixel = document.createElement('div');
pixel.className = 'Ink';
pixel.style.borderTopColor = c;
pixel.style.left = x + 'px';
pixel.style.top = y + 'px';
parentObj.appendChild(pixel);
}
As you can see in the code snippet, and a new element (object) is created, the Ink className is assigned to it, the border top is set, the width and height are both set to 1px, the pixel location is set, and then the pixel is assigned to a parent object (we?ll discuss this a bit later).
The Ink class is as follows and it's rather simple.
.Ink {
position: absolute;
background-color: #fff;
border-top: 1px solid transparent;
width: 1px;
height: 1px;
}
The above JavaScript and CSS snippets are simplified version of what you could actually use. In reality you would customize both to suit your needs, or you could keep these simple versions, creating other graphics mechanisms to rely on them. One example I'm thinking of is a rectangle. With regard to the mechanism I just described, a rectangle in this context is just a pixel with a width or height greater than 1px. So, you could rewrite the PlotPixel method to actually be a DrawRect method and create pixel with DrawRect(x, y, 1, 1, c), though it's better practice to write that method separately though.
Now, the most obvious question is where did this pixel get places? As we see from the above pixel code, it was appended to the parentObj object, but what is that? This is the root in the DOM tree that is going to house this child pixel. In practice it would either me another DOM created object, like a canvas object (discussed later) or a hard coded object in the structure of the page. It?s actually a good practice to keep a hard coded object in the structure of your page so that dynamically created objects have a common root. I refer to this special object as a dynamic attacher.
All you have to do to create a dynamic attacher object is create a simple div which will sit immediately after the body opening. If you put it anywhere else in the body object then you will probably end up having object positioning problems. This is because whatever is above the object adds to the object's positioning. So if you put your page's <h1> prior to the dynamic attacher, then your objects are going to shifted by the size of the <h1>.
So, put the following code immediately after the start on the body object.
<div id="dynamicAttacher"></div>
To utilize this object, you simply have to get a JavaScript reference to this object.
var dynamicAttacherObj = document.getElementById('dynamicAttacher');
In the context of the above pixel function, the JavaScript object which references the XHTML object was called parentObj. Since the DOM is a tree, you can nest objects under other objects. The dynamic attacher doesn?t have to be the parent of the pixel, but it should be the root of your graphics tree. In fact, it should really only have one child and all further objects should be added to that child (which of course then, makes it a parent.)
That's about all there is to it with regard to creating pixels. With that one function you have a new world of possibilities. Years ago, many people used a similar type of method to create simple effects like snow fall. However, all the effects that I've ever seen followed the old HTML paradigm of thinking. The method I'm using here is standards compliant and follows the Ajax (Asynchronous JavaScript with XML) model of thinking. But, you don't have to stop with pixels though. What about lines?
According to geometry, a line is a straight 1-dimensional object with no thickness and extends infinitely in both directions. Well, we won't be creating anything with no thickness and we sure aren't going to even imagine creating something of infinite length, but what we can do is create line segments with thickness.
Creating a horizontal or vertical line is rather simple and if you know anything about graphics development at all then you know that you simply have to iterate through a loop n-times where n is the length of your line. This is true, but in web development it's much easier. While in classic graphics development a box is created by many pixels, in web development a pixel is created by a box. In other words, we can simply create an elongated pixels.
You might think that a method to create this elongated pixel is nothing more than the DrawRect method I mentioned earlier, but that won't work since legacy IE6 can't handle div objects of unit height. So, we actually need to create DrawHorizontalLine and DrawVerticalLine methods. They might look something like this.
function DrawHorizontalLine(x, y, l, c) {
var longPixel = document.createElement('div');
longPixel.className = 'Ink';
longPixel.style.borderTopColor = c;
longPixel.style.width = l + 'px';
longPixel.style.left = x + 'px';
longPixel.style.top = y + 'px';
parentObj.appendChild(longPixel);
}
function DrawVerticalLine(x, y, l, c) {
var longPixel = document.createElement('div');
longPixel.className = 'Ink';
longPixel.style.border = '0';
longPixel.style.backgroundColor = c;
longPixel.style.height = l + 'px';
longPixel.style.left = x + 'px';
longPixel.style.top = y + 'px';
parentObj.appendChild(longPixel);
}
Do you see the difference between the two methods? The horizontal line basically created an elongated pixel, while the vertical line is created by a completely different method entirely. If you recall, the PlotPixel method used a transparent background with a colored top border to create a pixel. If we were to try to create a vertical line using that method, then we would get into trouble really fast since only the top of the line would be colored. So we actually had to create a div object with no border, a colored background and a height of the length.
What about lines that are diagonal? First off, the following is not going to help anyone.
function DrawLine(x1, y1, x2, y2, c) {
var longPixel = document.createElement('div');
longPixel.className = 'Ink';
for(var x = x1; x < x2; x++) {
for(var y = y1; y < y2; y++) {
plotPixel(x, y, c);
}
}
}
The reason this isn't that useful is that first off it's creates a box. Secondly, even if it you were to create something that created something like a line using a simple 'for' loop, you could only create a perfect 45 degree angle because of the fact that the screen is not continuous, but is rather made up of discrete units. To create a computer graphics line properly, you would use Bresenham's Line Algorithm as created by Jack Bresenham in 1962 at IBM. The algorithm uses some simple algebra to approximate the most accurate line possible. Here is the appropriate DrawLine method.
function DrawLine(x1, y1, x2, y2, c) {
var steep = Math.abs(y2 - y1) > Math.abs(x2 - x1);
if (steep) {
t = y1;
y1 = x1;
x1 = t;
t = y2;
y2 = x2;
x2 = t;
}
var deltaX = Math.abs(x2 - x1);
var deltaY = Math.abs(y2 - y1);
var error = 0;
var deltaErr = deltaY;
var xStep;
var yStep;
var x = x1;
var y = y1;
if (x1 < x2) {
xStep = 1;
}
else {
xStep = -1;
}
if(y1 < y2) {
yStep = 1;
}
else {
yStep = -1;
}
if(steep) {
PlotPixel(y, x, c);
}
else {
PlotPixel(x, y, c);
}
while(x != x2) {
x = x + xStep;
error = error + deltaErr;
if(2 * error >= deltaX) {
y = y + yStep;
error = error - deltaX;
}
if(steep) {
PlotPixel(y, x, c);
}
else {
PlotPixel(x, y, c);
}
}
}
For more information on Bresenham's Line Algorithm, check Wikipedia or any online computer graphics tutorial.
Not only do you now have the ability to create a line, you also have the powerful ability to create a vital graphics component. The most basic component of a polygon is a line. Now that you know how to create lines, using them to construct shapes should be easy.
Creating a polygon is as simple as drawing the start of one line at the end of another. You can create closed or open, complex or simple, or even wild and erratic objects using this method as the basis. But, what about circles? Given that a circle in Euclidean Geometry is an infinitely sided polygon you should definately use the line method, but how about we try something different?
Creating a circle is one of the easiest things anyone can do in computer graphics. At least it was 15 years ago when I would create them in game development. These days our screen resolutions have gotten a bit better than the 320x200 I used to work with. Let's begin by examine how we used to create circles using that old school method.
This method relies on the fact that you can create a circle by rotating a line around a center point and plotting the end of the line, much the same way you would draw a circle with a compass. In computer graphics however, we will rely on trigonometry, not on a physical a device.
The basic math behind this method is simple. Given a center point of P(x,y) and a radius r and angle a, you can find the point on the circle C(x,y) simply by finding the components x and y where x is r + Cos(a) and y is r + Sin(a). That's great, but this isn't the middle ages or third grade so we are going to be using radians instead of angles. Now, given a in angles we now have C(x, y) in radians where x is r + Cos(a*pi/180) and y is r + Sin(a*pi/180). In practice, you simply have to plot the pixel C(x, y) at every angle from 0 to 359.
Graphics.prototype.DrawCircle = function(xc, yc, r, c) {
for(var a = 0; a < 360; a++) {
PlotPixel(r * Math.cos(a * Math.PI / 180) + xc, r * Math.sin(a * Math.PI / 180) + yc, c);
}
}
On a 320x200 resolution screen this shows a perfectly enclosed circle, however not on our modern screens. The reason this worked 15 years ago, but not today is because this method draws only one point for every degree in a circle. For a lower screen resolution every one of these points would touch each other, however on our modern screen this leaves enormous gaps between the circles.
So what do we do? The answer simply lies in geometry. I'm not going to explain the math behind this, but I will say that it uses the 2D circle formation and symmetry. You should be able to find a good explanation of modern methods of creating a circle online, but here is the code for it.
function DrawCircle(xc, yc, r, c) {
var x = xc - r;
var y = yc - r;
yc = yc - r /2;
xc = xc - r;
circle.style.left = x + 'px';
circle.style.top = y + 'px';
circle.style.width = r * 2 + 'px';
circle.style.height = r * 2 + 'px';
var r2 = r * r;
x = 1;
y = parseInt((Math.sqrt(r2 - 1) + 0.5));
PlotPixel(xc, yc + r, c);
PlotPixel(xc, yc - r, c);
PlotPixel(xc + r, yc, c);
PlotPixel(xc - r, yc, c);
while (x < y) {
PlotPixel(xc + x, yc + y, c);
PlotPixel(xc + x, yc - y, c);
PlotPixel(xc - x, yc + y, c);
PlotPixel(xc - x, yc - y, c);
PlotPixel(xc + y, yc + x, c);
PlotPixel(xc + y, yc - x, c);
PlotPixel(xc - y, yc + x, c);
PlotPixel(xc - y, yc - x, c);
x += 1;
y = parseInt((Math.sqrt(r2 - x*x) + 0.5));
}
if (x == y) {
PlotPixel(xc + x, yc + y, c);
PlotPixel(xc + x, yc - y, c);
PlotPixel(xc - x, yc + y, c);
PlotPixel(xc - x, yc - y, c);
}
}
Looking at it you will actually be able to see the symmetry it's doing. It will plot a pixel at C(xc + x, yc + y) then at 7 other mirrored points on the circle. You may also notice the r2 = r * r (r squared) in there and recall from a Calculus or advanced geometry course how a circle is actually defined.
You can do more complex things with JavaScript graphics development, but you have to remember that each pixel is actually a div object. So an implementation of a flood fill algorithm used to fill areas with a certain color may not be something you will want to manually do any time soon. For things like that, you may want to switch to SVG graphics as seen in Firefox 1.5.
As far as performance of web browsers, Gecko, Firefox's rendering engine, is currently the fastest of the web rendering engines on the market and it will be able to take on many of the graphics development projects you throw at it. Trident, IE6's rendering engine should also be able to handle it, but you should take special care when even thinking about offering any Opera support. Opera's rendering engine, Presto, is the slowest of these web rendering engines and it's not just slow, it's really slow. You won't really be able to do any manual graphics creation using Opera's engine, so you may actually want to give them a stop sign and a redirect to getfirefox.com to help them along.
Now that you have seen some of the basics objects you can create using JavaScript Graphics Development, what about interaction of the objects? This is actually very easy in standards-compliant browsers (i.e. Firefox/Mozilla), but can get rather weird in IE and Opera (especially Opera.), so I'm really only going to demonstrate the proper way of doing this without using any special IE or Opera tools.
Before I explain actual interaction, you will need to understand the concept of a canvas object. Without a canvas, you simply have a ton of objects all over the place with no connection between them. A canvas will allow you to group your pixels together in a larger parent object. You could theoretically group your pixels by giving them a common id prefix or create a virtual attribute identifying them with each other, but the DOM is a tree and to group things together they need only be on the same branch. A canvas is that branch.
To create a simple canvas you could use something like the following code.
function CreateCanvas(w, h, id) {
var canvas = document.createElement('div');
canvas.className = 'Absolute';
canvas.id = id;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
canvas.PlotPixel = function(x, y, c, id) {
var pixel = document.createElement('div');
pixel.className = 'Ink';
pixel.id = id;
pixel.style.borderTopColor = c;
pixel.style.left = x + 'px';
pixel.style.top = y + 'px';
if(x <= parseInt(canvas.style.width) &&
y <= parseInt(canvas.style.height)) {
this.firstChild.appendChild(pixel);
}
}
canvas.SetLocation = function(x, y) {
canvas.style.left = x + 'px';
canvas.style.top = y + 'px';
}
canvas.CreateBorder = function(width, style, color) {
this.style.border = width + ' ' + style + ' ' + color;
}
canvas.CreateContainer = function(w, h, id) {
var container = document.createElement('div');
container.className = 'Absolute';
container.id = id + 'Container';
container.style.width = parseInt(w) + 'px';
container.style.height = parseInt(h) + 'px';
return container;
}
canvas.CleanCanvas = function( ) {
if(this.firstChild) {
this.removeChild(this.firstChild);
}
this.appendChild(this.CreateCanvasContainer(this.style.width, this.style.height, this.id));
}
canvas.appendChild(this.CreateContainer(w, h, id));
return canvas;
}
Did I say simple? Well, ok, maybe it looks a bit complicated, but it really is simple. This method will create a canvas object and then return the canvas object. That's the gist of the matter, but as you can see there's more going on. The first thing you will notice is the internal PlotPixel function on the canvas. If you have never worked with JavaScript OOP, this is going to seem really odd. In JavaScript you can actually assign methods just like you would any variable. Other languages do this too, but they may have a special name for this process. C#, for example, calls these types of methods, anonymous methods. Regardless of what you call it, you're creating a function.
In fact I'm not just creating one function, but a few. The first is our new PlotPixel method, the second SetLocation actually moves the canvas to a place on the screen and the third, CreateBorder, is really just an added bonus for creating a canvas border. Then there is a function, CreateContainer, used to create the container object which will actually hold the pixels and finally a cleaning method, CleanCanvas, used to clear off the canvas.
There is one thing that I should definitely explain. In the PlotPixel function, there was the following code:
if(x <= parseInt(canvas.style.width) &&
y <= parseInt(canvas.style.height)) {
this.firstChild.appendChild(pixel);
}
The conditional is actually implements clipping. Clipping is where something is only seen within a certain area. For instance, in a video game where an enemy walks half way behind a wall, only half of the enemy is seen. The other half is clipped. Here, this makes it so that only pixels within the canvas area are drawn, therefore saving memory and helping performance out a bit.
The second thing to notice in that code snippet is the this.firstChild line. This line actually assigns the newly created pixel to the container object of the canvas, this container object contains the actual content of the canvas. If you look at the canvas function again, you will see that the last thing it does before returning the object is create a container object and assigns it as a child of the canvas. Since this is a newly created canvas object, this is the first child. Thus, by adding the pixel to the first child of this canvas, the pixel is actually being added to the container of the canvas, which is exactly what this line is doing.
To use a canvas you simply have to create one and then append objects to it's canvas. Here is how to create the canvas:
var canvasObj = CreateCanvas(w, h, id);
I know, that's rather obvious. You simply give it the width, height and what ID you want to call it. That's it. To draw a pixel on the canvas, you simply call the canvas.PlotPixel method. The pixels are then automatically applied to the canvas. But what about actually seeing the pixels? Adding them to the canvas is great, but we never assigned the canvas to anything. If you recall, you have to actually assign the object as a child of the dynamic attacher object discussed earlier, or to something that is attached as a child of the dynamic attacher object. So let's create an Attach method to help us out.
function AttachObject(parentObj, childObj) {
parentObj.appendChild(childObj);
}
You just give it a parent and child object and it applies it for you. So in practice you could do the following.
var canvasObj = CreateCanvas(100, 100, id);
canvasObj.SetLocation(0, 0);
canvasObj.CreateBorder('1px', 'solid', '#000');
canvasObj.plotPixel(10, 10, '#000');
canvasObj.plotPixel(10, 11, '#000');
canvasObj.plotPixel(10, 12, '#000');
canvasObj.plotPixel(10, 13, '#000');
canvasObj.plotPixel(10, 14, '#000');
canvasObj.CleanCanvas( );
canvasObj.plotPixel(10, 10, '#000');
canvasObj.plotPixel(11, 10, '#000');
canvasObj.plotPixel(12, 10, '#000');
canvasObj.plotPixel(13, 10, '#000');
canvasObj.plotPixel(14, 10, '#000');
AttachObject(dynamicAttacherObj, canvasObj);
This is actually a horizontal line since we are plotting pixels vertically, clearing the canvas off, and then plotting pixels to the right. The rest should be fairly self explanatory.
You could expand on this example by adding the line and circle methods to the canvas, or you could go even further and create all your geometric shapes under the canvas class to allow everything to be canvased and therefore encapsulated as a larger entity. Speaking of larger entities...
What if you did create some entity using a canvas. What about manually moving the entity? This is actually rather easy. Due to the tree structure we have been working work, JavaScript allows us to move simple objects as well as complex objects. That is, given that the object has a way to be found (i.e. by an id) we can move the object directly or we can move the branch the object lies on, there by moving all the branches child objects including this object. By adding this functionality, we have effectively turned a basic object into an entity known as a Widget.
So where do we start with interaction and the creation of a simple widget? There's really only two things to do: make sure the object has an id and do capture some events like mousedown, mouseup, and mousemove . I'm actually only going to show the very basic way of doing this. In reality, you would probably actually add more error checking and as well as other basic windowing features but this will do for now.
function ultraSimpleMovement(obj) {
obj.moving = false;
obj.addEventListener('mousedown', function(evt) {
obj.layerX = evt.layerX;
obj.layerY = evt.layerY;
obj.moving = true;
}, true);
obj.addEventListener('mouseup', function(evt) {
obj.moving = false;
}, true);
obj.addEventListener(obj, 'mousemove', function(evt) {
var x = evt.clientX;
var y = evt.clientY;
if(obj.moving) {
obj.style.left = (evt.clientX - obj.layerX) + 'px';
obj.style.top = (evt.clientY - obj.layerY) + 'px';
}
}, true);
}
As you can see by the use of addEventListener, this code is only for standards compliant browsers and systems. Thus, if you really need to have IE6 support, then you will have to add a few tests in there as well as use a few non-standard Microsoft DOM methods and events.
There are a few parts to this code, but most should be self explanatory. The basic mechanics work something like this...
Create a virtual attribute called moving which is set to false.
Capture mousedown and on mousedown set that virtual attribute to true.
Capture mouseup and on mouseup set that virtual attribute to false.
Capture mousemove and if the moving virtual attribute is true, move the object.
Those are the obvious parts though. The less obvious parts involve the use of both evt.clientX (and evt.clientY) as well as evt.layerX (and evt.layerY). evt.clientX and evt.clientY actually tell you where the mouse is with relation to the client are, while evt.layerX and evt.layerY tell you where the mouse is with relation to the current object. Why am I using both here? If you didn't use the second ones, when you moved an object your mouse would immediately jump to the upper left of the box since you are moving the upper left, top part of the box to that location. You need to subtract where the mouse is currently from where it is on the screen to keep the mouse 'grabbed' where you originally 'grabbed' it.
In a production system, however, you would use something a bit more robust to account for the mouse moving too quickly out of the box area as well as other considerations. You would also probably want to add more logic to control where the widget could be placed and how the widget may be moved. Perhaps you only want people to move the widget by a handle on the top, or maybe you want the entire thing movable by any part. Whatever you would like to do, the ideas shown above are a good foundation.
So what could you do with all this? First off, the widgets are self explanatory. You could create a system with a floating calendar . If you were to link techniques similar to these to other Ajax principles like asynchronous processing (i.e. XmlHttp), you could allow users to design their own portals without ever doing a single post back. This is very similar to what Google dos for their portal and what Microsoft does with live.com.
Non-widget applications, however, may be more up your alley. One sample application I built is a JavaScript sin-wave example. This example shows how you can plot various parallel sin-waves using JavaScript graphics. You could extend this application by creating a trigonomic graphing calculator. You could use the same techniques to do simple animations, visual-aide demonstration, or even web assistants on your websites, all without images.
Another example I've built is a JavaScript graphs and chart library. The example isn't so much an example as it is what I'm using on my personal website to render data. It was created completely using JavaScript and the DOM. In fact, the only hard-coded item in the page structure is my dynamic attacher object. The library also has widget capabilities.
The most obvious graphics technology is Macromedia Flash. However, using Flash has many disadvantages including the requirement to purchase Macromedia Flash, the high learning curve of Flash, as well as a proprietary graphics format. With Flash you create the graphics and then interact with them. The Flash animations are in a Flash format and have no code representation, so, editing Flash requires Macromedia Flash.
One technology that is increasing in popularity is SVG or Scalar Vector Graphics. Using SVG, web developers can display vector-based, instead of plain raster-based images in a web environment. Using SVG programmers have full access to the code of the graphics. That is, unlike Flash, SVG graphics are in code which allows for easy and powerful editing. Firefox 1.5 is the first major web browser to provide native SVG support. Interaction with SVG is done via JavaScript, which can either be an advantage or a disadvantage depending on if you are a JavaScript expert or not.
Another great web graphics technology is WPF/E. WPF, Windows Presenation Foundation is Microsoft's unified graphics system component of the .NET Framework 3.0. It's used to create anything from business applications to more complex 3D games with a single unified graphics programming interface. WPF/E, Windows Presenation Foundation/Everywhere, is Microsoft's technology that takes the WPF out of the Windows world and out to the world. WPF/E shares the same easy to use unified .NET programming interface as WPF, minus 3D capabilities. WPF/E works not only in Windows and IE, but also in Mozilla Firefox, Apple Safari, and well, everywhere else basically. Using WPF a developer could create a powerful desktop application using the very intuitive, elegant, and powerful C# programming language and then publish the application on a website allowing Firefox and Safari users to get in on the action. In addition, WPF/E components in a web browsers are controllable by JavaScript. For these reasons, WPF/E has been called "the Flash killer".As you can see, despite the myths about JavaScript you can do graphics development. All that's requires is knowledge of the DOM and knowledge of JavaScript. These skills combined with Ajax paradigms, you too can create powerful web component as seen on Google portal and live.com. Furthermore, you can use these kills plus Ajax paradigms in applications integrating SVG or WPF/E components to create even more powerful web applications.
http://www.davidbetz.net/graphics/