Cursors in OpenLayers

One of the key features in software that feels solid and intuitive is the number of cues it returns to the user about what it's doing or what mode (or mood!) it's in. This is especially important for web-based applications because network problems cause delays in responses sometimes leaving the user a bit confused as to what's wrong and what to do about it. Software that doesn't supply enough feedback often feels shaky, unpredictable, and unintuitive. When nothing's happening, you can't have enough cues to tell the user that it's the network and not the software that's constipated, or that it's waiting for you to draw a line.

Just look at your browser with eyes wide open sometime as it's making a difficult network connection. In Firefox, when it's busy there's a spinning icon in one corner, a moving progress bar in another, in the third corner the x next to the reload button has turned red, in the lower left an active text status message is reporting the situation live as it's happening, and the cursor has changed to the "wait" state. All this goes into giving you that intuitive feel that the software is smart, rock solid, and and all is well, albeit slow.

Since we deal with delivering bandwidth-straining digital maps to an audience that's often unsophisticated about mapping issues, you can't include too many cues to let people know what's happening and continually remind them what they can do, and building smart cursors into your application is a simple thing you can do that goes a long way towards keeping the user from getting bored or lost. And so, in an amazing coincidence, this article talks about how to use cursors with OpenLayers.

One of the new features in OpenLayers 2.8 is that you can specify standard and custom cursors for your application entirely with CSS (Cascading Style Sheet) styling. In this tutorial, we'll take a look at the basics of how to do this, provide a working example, and finish up with a description of how you can create and use your own customized cursors. A reference list of all the cursor codes is also included at the end. Also some working examples with source code you can download are attached to this as well.

Learning how to do things in OpenLayers can be challenging since it is developing faster than it can be documented. Since I'm no expert with JavaScript and still new to the OpenLayers library, I want to thank all the helpful people on the mailing list who get me past the rough spots and offer a special thanks to Andreas Hocevar and Bart van den Eijnden for their generous help setting me straight on how to use cursors in OpenLayers. Also, since I am still learning how to use OpenLayers, you may find mistakes in this article. Please feel free to correct, rearrange or add to what ever I've got wrong or have left out.

The Basics

You can change the look of things in OpenLayers by simply assigning a CSS style to its displayClass property. So you first need to know how to use CSS markup. I won't cover that here, but if you need help with CSS, then go to a web site like  w3schools and learn something new.

So let's take a look at some cursor definitions that are pre-set in OpenLayers. You'll find these in the OpenLayers distribution directory in theme/default/style.css. This file is worth a closer look as it contains CSS style definitions for just about everything that OpenLayers shows to the user, but we're interested in cursors, so search for the word "cursor".

After looking at a few examples, you see that assigning one of the built-in cursors to an element is pretty simple. It's just cursor : cursor-name. The hard part is figuring out what the class name is for say, the MousePosition control. But as it turns out that's not so hard either.

How To Find Or Determine The Display Class

Here's the trick: All OpenLayers controls are named using a standard convention, and their displayClass properties are determined by that name. The method for converting a control's full name into a displayClass value is to replace the "OpenLayers" with ol, append the class name ("Control" in this case) and the control's unique name, remove any punctuation and CamelCase the result. For controls, you also need to append the word "Active" and that will give you the displayClass name for that control when it's activated by OpenLayers.

For example, if you want to assign the Pointer cursor to the OpenLayers.Control.MousePosition control when it's been activated, the displayClass is ol + Control + MousePosition + Active, and the CSS would look something like this:

.olControlMousePositionActive {
    cursor : pointer;
}

So now when the mouse position control is activated the cursor will change from the default arrowhead to the pointing hand.

Overriding the displayStyle

A common way to provide "zoom in" and "zoom out" functionality in OpenLayers is to use the ZoomBox control for both of these tools (ZoomBox can handle both zooming in and out, depending on how its parameter variable "out" is set). But because we need to use different images for each tool, we can't use the same display class for both. So what we do is override the displayClass for the ZoomOut tool and make up a name for it different than "olControlZoomBox" which we're already using for the Zoom In tool.

So to set up the Zoom Out tool to also use the ZoomBox control, we create it just like we did for the Zoom In tool, except that now we have to use a different name for the displayClaas, so let's pick 'olControlZoomBoxOut' to stay as consistent as possible to the OpenLayers object naming convention. We'll also have to reverse the direction of zoom, and that's done by setting the "out" variable to true. Both of these tasks are done by passing the displayClass and out variables to the tool's Constructor via a hash table object when you construct the tool. The code might look something like this:

toolButtons[1] = new OpenLayers.Control.ZoomBox(
{
  title : 'Zoom In',
});

toolButtons[0] = new OpenLayers.Control.ZoomBox(
{
  title : 'Zoom out',
  out : true,
  displayClass:'olControlZoomBoxOut'
});

Finally, we just have to assign the cursor and tool button images to the appropriate displayClass. So in the CSS section of your application, you might do it this way:

.olControlZoomBoxActive { 
  cursor: url('zoomIn.gif') 12 10, crosshair;
}
.olControlZoomOutActive { 
  cursor: url('zoomOut.gif') 12 10, crosshair;
}

.olControlPanel .olControlZoomBoxItemActive {
  background-image: url("zoomIn-on.bmp");
  background-repeat: no-repeat;
}

.olControlPanel .olControlZoomBoxItemInactive {
  background-image: url("zoomIn-off.bmp");
  background-repeat: no-repeat;
}

.olControlPanel .olControlZoomBoxOutItemActive {
  background-image: url("zoomOut-on.bmp");
  background-repeat: no-repeat;
}

.olControlPanel .olControlZoomOutItemInactive {
  background-image: url("zoomOut-off.bmp");
  background-repeat: no-repeat;
}

The first two assignments are for the cursor that you see moving on the screen when the tool is activated. The first of these uses the usual code you'd use for a control, while the second uses our made-up displayClass name, and this keeps the graphics separate. The other four assignments are to change the graphic icon on the buttons themselves as the display status changes. The choice of displayClass base name is completely arbitrary. You could use your own codes for all the tools you use if you want, and you need only remember that OpenLayers will append "ItemActive" or "ItemInactive" to this base name depending on the state of the tool, so be sure to include a CSS displayClass assignment for each case.

In the attached source code file, CursorDemo2.html shows a working example that implements zoom in and zoom out using these methods.

Custom Cursors

There are a couple of ways you can get new cursors for your applications. First, you can download either the cursors directly from any number of web sites or download a utility to make them from scratch (see Downloadable Resources below). If you're using a CSS-2 compliant browser such as Firefox then you can use plain bitmaps as cursors. Simply set the cursor style using the url syntax: cursor:url( <bitmap filename or uri> ) hotspot-x hotspot-y, e.g. cursor: url(../img/zoomIn.gif) 9 9. The image file can be a full or relative path name or a hyperlink that points to the image file. The two following numbers are the coordinates of the pixel in the image that will be used as the cursor's reference point, or "hotspot." Note that the point 0 0 is the pixel in the upper left corner of your image.

Since you can define several alternate styles for your cursors you can try your fanciest cursor first, but if that's not supported by the user's browser, you can specify simpler and simpler cursors. If you use the url syntax to define a cursor, you should ALWAYS define a final cursor that is guaranteed to work in all browsers, such as 'auto' or 'default'. So in practice the example above really should have been:

cursor: url(../img/zoomIn.gif) 9 9, default;

Example - Using Different Cursors in OpenLayers

To see a working example of various cursors, set up the Cursors Demo map (see attached file, ol_cursors.zip). This is a pretty generic OpenLayers map, but the interesting bits are in its source code in the CSS style section.

.olControlZoomBoxActive { 
   cursor: url(zoomin.gif) 12 10, crosshair;
  }
	
.olDragDown {
   cursor: url(Colors.ani), url(FatPointer.cur), pointer;
  }

This is where we define the cursors for the various events triggered during a map zoom operation. The first cursor is invoked when the zoomBox control is activated, and the displayClass is set to olControlZoomBoxActive. We define two cursors here. The first is the tool cursor which is just a bitmap image of a magnifying glass, with the hotspot set to 12,10 on a 32-pixel grid. Since IE users won't be able to see this cursor type, the second alternative is the built-in crosshair cursor which is visible in all browsers.

Finally, When the user drags the box to define the area to zoom to, the cursor switches to custom-made cursors. The first choice is an animated cursor, but this is not visible by browsers other than IE, so the fall back is the FatPointer cursor, a custom-made static cursor, visible to FireFox and other CSS-2 compliant browsers. And for browsers that can't handle custom cursors at all, the reference to the built-in Pointer cursor is for them. Always make sure that you include an option that's guaranteed to work with any browser.

Limitations

All browsers are not created equal. Capabilities and features vary widely depending on the browser's manufacturer and even what version it is. So it should be no surprise to learn that Microsoft's Internet Explorer supports fancy animated cursors while Mozilla's Firefox sticks to the CSS standards and does not. But Firefox does support custom image cursors while Internet Explorer doesn't.

Whether the browser's developers are offering innovative features before they become a web standard or they're just showing a flagrant disregard for standards is a matter of opinion, but the fact is that if you use a cursor not supported by all browsers, you should take care that you use a reasonable default for those whose browsers don't support the fancy cursor you want to use.

Cursor Type Codes

The following is a list of most of the different cursor types you're likely to encounter. Hover the mouse over the cursor style code to see what it looks like in your browser.

Cursors Supported By All Major Browsers

These cursor styles are supported by all the major browsers, so they are cross-platform safe. However, the icon will look a little different depending on the browser.

auto This is the default in which the browser sets the cursor
crosshair The cursor changes to a crosshair icon
default The default cursor
help This cursor hints that help is available on anything clicked
move Indicates the application is in "move mode"
pointer This cursor is used for pointing
progress Provides feedback to the user on progress
text The cursor is used to select text
wait This cursor means that the program is busy

CSS3 Compatible Browsers

These cursor styles are new and only available on browsers that are CSS3-compliant.

url A comma separated list of URLs to custom cursors. Note: Always specify a generic cursor at the end of the list, in case none of the URL-defined cursors can be used. Example: `cursor : url( "http://example.com/cursors/fave.cur", "fancy.aml", auto );`
inherit Specifies that the value of the cursor property should be inherited from the parent element

Resize Cursors

These cursor styles are used for indicating that they've "grabbed" a feature's bounding box edge and are ready to expand or shrink the object.

e-resize Ready to drag the right edge of a box
n-resize Ready to drag the top edge of a box
ne-resize Ready to drag the upper right corner of a box
nw-resize Ready to drag the upper left corner of a box
s-resize Ready to drag the bottom edge of a box
se-resize Ready to drag the lower right corner of a box
sw-resize Ready to drag the lower left corner of a box
w-resize Ready to drag the left edge of a box

Suggestions For Further Reading

From W3C standards library, see  'cursor' property in the Mouse and Keyboard section of the CSS3 Specification.

 W3 Schools CSS tutorial

Mozilla offers  Using URLs for the Cursor Property

Downloadable Resources

Build your own cursors:  JustCursors1 Free Windows software to build custom .CUR and .ANI cursors.

About Icon/Cursor files:  http://www.delphipages.com/articles/icon__47_cursor_files-9235.html

Attachments