Cross Browser CSS cursor Images In Depth

December 21st, 2011 by zoltan · 15 Comments
brush 2
line
rectangle
circle
clear
new

Paint widget a remix of CanvasPainter © Rafael Robayna

If you are using a desktop browser (except for Opera), play with the paint widget on the left. When you select a tool and mouse over the white canvas, your mouse arrow will change to a custom cursor representing that tool (à la Photoshop or The GIMP). This is not done by creating a DOM object and moving it to the mouse’s coordinates — we are using CSS to do it using the cursor property’s url() function. When used properly, custom CSS cursors can add a little bit of polish to your web sites and applications. However, doing this in a cross browser way can be a little confusing unless you know all the gotchas, and this article will go into depth about them. We will also explore issues such as when to use custom cursors, performance, what makes good cursor design, cursor file formats, and cursor size.

When Should I Use Custom Cursors Instead of the Built-in Ones?

In general, CSS cursors (built-in or custom) should be used as a hint to the user as to what action the mouse can perform. Let’s take a look at an example that doesn’t use custom cursors to illustrate a common use-case: drag and drop. To give users a visual cue that an item is draggable, it is common practice to set an object’s CSS cursor property to move. Mouse over the object draggable object below to see how this works.

This is done with the following CSS:

a[draggable="true"] {
    cursor: move;
}

The code above ensure that when a user “mouses over” a link with the HTML5 Drag and Drop “draggable” attribute, the move cursor appears. It is a great way to help the user “figure out” how to use the user interface with minimal instruction. A list of the built-in cross-browser cursor property values can be seen at CSS2 Cursor Style Page.

The built-in cursors are great, but there are a few things to keep in mind.

  • Built-in cursors may look different depending on what browser/operating system is being used. For example, some browsers (e.g. Firefox on Windows 7) will show the move cursor as a four-pointed arrow ([Four-Pointed Arrow Cursor]) while others (e.g. Firefox on Mac OS X) will show a hand ([Hand Cursor]). Using custom cursors, you can ensure all applications are using the same cursor for a more consistent expeience. The example below is the same as the one above, except in all browsers, you will see the a hand icon.
  • Not all browsers support all the same “built-in” cursors, and custom cursors allows you to add support for them. For example, while Firefox on Windows doesn’t support context-menu, it seems to be the only browser that supports the zoom-in and -out cursors. Using custom cursors, you can implement all of these in all browsers.
  • There may not be a built-in cursor for the use-case you need to solve. A good example is in the paint widget at the beginning of this article — none of those cursors exist natively in any browser.
  • It’s nice to design your own cursors, since sometimes the built-in ones may be a little basic looking (If you do this, please keep in mind that users are used to the built in ones, so your custom cursor shouldn’t look too much different than them if you want your application to be easy-to-use).

The CSS of Custom Cursors

Here is some sameple CSS code that will show custom cursor when the user mouses over a div with an id of dragMe. If the browser doesn’t do custom cursors (like Opera), the cursor fallback to the built-in move cursor.

#dragMe {
    cursor: url('customMoveCursor.cur'), move;
}

As long as your cursor is in the same directory as your stylesheet, and as long as it is an uncompressed .CUR file, it’s as simple as that. Note that a .CUR file is just an .ICO file with extra information that allows the developer to define the “host spot” position of the cursor (i.e. the part of the image which points to the position of the mouse). Note that .CUR files support 32-bit color (16.7 million colors plus alpha channel transparency), so designers can create cursors that have semitransparent areas like shadows and anti-aliasing.

How does one create a .CUR file? Not too many graphic tools create .CUR files natively, but there are some easy solutions. If you are a Photoshop user, the ICO (Windows Icon) file format plugin for Photoshop is what you are looking for (I have not used it myself, but it apparently can save .CUR files directly. Any comment on how well this works would be most welcome). If, like me, you use the GIMP, simply save the file as a .ICO file and convert it with this command line tool written in Python by Jan Thor.

The Gotchas of Custom CSS Cursors

There are a few things you need to remember when using CSS cursors

  1. You must add a default “built-in” cursor after your custom cursor, or the custom cursor will not load in Firefox. Think of it as Mozilla’s way of enforcing good web practices. :-)
  2. Internet Explorer interprets relative URLs as relative to the HTML document, and not the CSS file like God (and the W3C) intended (Sometimes it seems that IE goes out of its way to make lives difficult for us developers). This is true for all versions of IE, even IE9. To ensure cross-browser compatibility, you must either use an absolute URL:
    #dragMe {
        cursor: url('/cursors/customMoveCursor.cur'), move;
    }
    

    or a fallback url for IE:

    /*
     * Assume this the HTML is in a directory above this CSS file
     */
    
    #dragMe {
        cursor: url('../cursors/customMoveCursor.cur'),     /* Modern browsers    */
                url('cursors/customMoveCursor.cur'),        /* Internet Explorer  */
                move;                                       /* Older browsers     */
    }
    
  3. It is best that your .CUR files are 32×32 pixels in size. IE9 seems to resize cursors that are smaller than this to 32×32, and IE8 and under cannot show cursors larger than this size. While it is true that you can fit multiple file sizes inside a .CUR file, sticking with one 32×32 image will ensure cross-browser consistency.
  4. Although .CUR files can be saved in either a compressed or uncompressed format, not all browsers can read the compressed ones. It is best to save your cursors in uncompressed format. If you are using the GIMP to save to .ICO format first before you convert to .CUR, make sure that the .ICO is saved without compression.

Performance Issues

As mentioned earlier, a lot of browsers (like Firefox or IE6) cannot show compressed .CUR files, but they can show .PNG files. Since we would like to use a compressed version if possible, one could do this:

#dragMe {
    cursor: url('/cursors/customMoveCursor.png'),      /* Modern browsers    */
            url('/cursors/customMoveCursor.cur'),      /* Internet Explorer  */
            move;                                      /* Older browsers     */
}

This works well as long as the cursor hotspot is 0,0. You can define a PNG hotspot using the CSS3 cursor syntax, but it breaks IE:

#dragMe {
    cursor: url('customMoveCursor.png') 5 15, /* Modern browsers, hotspot is (5, 15)            */
            url('customMoveCursor.cur'),      /* IE chokes on the above line .. never gets here */       
            move;                             /* Older browsers (IE never gets here either)     */
}  

In order to fix this issue, one must you conditional comments to make a separate IE from everyone else. Let’s use a variation of Paul Irish’s Conditional Comment pattern to do this. Change the html tag to this:

<!--[if (lte IE 9) ]><html class="ie9 oldIE">    <![endif]-->
<!--[if (gt IE 9)  ]><html class="modern">       <![endif]-->
<!--[!(IE)]><!-->    <html class="notIE modern"> <!--<![endif]-->

Now we can use this CSS to ensure .PNG loads in non-IE browsers:

html.modern #dragMe {
  cursor: url('customMoveCursor.png') 5 15, /* Modern browsers, hotspot is (5, 15).   */
          move;                             /* Older browsers                         */
}

html.oldIE #dragMe {
  cursor: url('customMoveCursor.cur'),      /* IE .CUR file loads                     */       
          move;                             /* In case IE can't load the above.       */
}  

The extra bytes used to create the conditional comments may or may not be worth the trouble. If you are using conditional comments (like I do all the time), it may be worth it.

A Final Word On Testing in IE

If you edit a .cur file on the web server and reload your page with that cursor in Internet Explorer, you may not see the edited change, since IE has kept the older cursor in cache and has a hard time letting go, kind of like that person you dated in high-school. You will need to clear IE’s cache in order to see the new cursor. This is quite annoying, and it is something you should keep in mind when troubleshooting in IE.

Further Reading

Tags: CSS · CSS3 · cursor · Images · , , ,

15 responses so far ↓
  • 1 Cursor Man // Dec 21, 2011 at 9:30 pm

    You can use cursor.cc to draw the .cur files.

  • 2 Mathieu 'p01' Henri // Dec 22, 2011 at 5:26 am

    FYI, Opera does NOT allow custom cursors for security and accessibility reasons.

    Custom cursors are a very easy and tempting way to fool users. Imagine a custom cursor that looks just like a normal one but offset by a few pixels. This can used in many many ways to trick people into clicking somewhere they don’t want to. Think click jacking but even more subtle.

    Also a custom cursor ( esp. one fully transparent or made of a few pixels ) could prove very difficult to see and use for people with vision impairment.

  • 3 zoltan // Dec 22, 2011 at 11:25 am

    @Mathieu: Interesting commentary — thanks! Is this Opera’s official position regarding custom cursors, or your own thoughts?

    I wonder if it is possible for browsers to support custom cursors and prevent the legitimate “click-jacking-like” situation like you describe. For example: perhaps the browser can look at the cursor and see if the hotspot is transparent, and that it is attached to a substantial part of the image. It would be great if Opera took the lead and showed how they could support this feature while taking care of potential abuses of the technology.

    As for the visual impairment issue you describe, it is definitely prone to that, and I think that some best practices can be developed to help developers in that area. But maybe that is a topic of another blog post. :-)

  • 4 Dylan Greene // Jan 14, 2012 at 9:48 pm

    You can also create cursors on the fly using Canvas. Doodle or Die does this to make different color and sized paint brush cursors instead of downloading hundreds of cursor images.

    http://doodle.no.de

    Note that this is currently disabled in Chrome because of this Chrome bug: http://crbug.com/109918

  • 5 Patrick Corcoran // Jan 15, 2012 at 12:32 am

    @Mathieu: Your hypothetical Black Hat custom cursor attack is interesting theoretically. But in practice I think it is a specious argument. JavaScript can already “click” things the user didn’t intend to click. If a web site is so untrustworthy as to deceive visitors with subtle click-near-here-but-exactly-here attacks, I’m sure they are already doing far worse things in script. (I’m ignoring the non-JavaScript use case, because real users never disable JavaScript. The class of users which are the paranoid elite are a powerful and important force among us, but they are also very few in number.)

  • 6 Mathieu 'p01' Henri // Jan 15, 2012 at 4:28 am

    @zoltan: It is Opera’s official stand on custom cursors. I wonder how the other vendors deal with the issues raised by custom cursors, if they even acknowledged the possible problems.

  • 7 zoltan // Jan 15, 2012 at 1:03 pm

    @Patrick: I can see both sides of this argument. Ensuring applications are secure requires looking outside what you immediately see and look at things the way hackers (good and bad) do, and I always make a point of not dismissing point-of-view like Matthieu’s. For example, who would have thought that the :visted CSS psudo-class could be used to steal a user’s browser history?

    @Matthieu: Thanks you for clarifying! That is a very interesting question. If anyone from the other browser manufacturers has any comment on this issue, please comment below. I’d love to hear everyone’s take on this. :-)

  • 8 Mathieu 'p01' Henri // Jan 16, 2012 at 5:19 am

    @Patrick: You know browsers DO check if clicks are triggered by the user or programmaticaly and apply security measures accordingly esp. when it applies to form elements or elements triggering a call to an API dealing with user data.

    In the vein of what @zoltan said above, when I made Defender of the Favicon, it didn’t cross my mind that favicons could be used to deceive and phish people during a tab napping. Since then I have learnt to look even further when thinking outside of the box.

  • 9 zoltan // Jan 16, 2012 at 1:05 pm

    @Mathieu: Thanks for the tabnabbing link. Yet another thing to keep in mind. On a side note, even though it uses technology that could be used for evil purposes, your Defender of the Favicon is pretty frickin’ aweome! :-)

  • 10 Fabian // Mar 31, 2012 at 8:32 am

    @Mathieu: Defender of the Favicon is friggin’ awesome man :D

  • 11 mcmwhfy // Apr 3, 2012 at 2:13 am

    Thank’s for this post, I don’t know why but for my project the performance is highly impacted if I use custom cursors :(( maybe because my project is based on wicket and java…
    css: *{cursor: url(../img/arrow1mc.cur),default;}
    can anyone give me a suggestion why this happen?

  • 12 zoltan // Apr 3, 2012 at 8:57 am

    @mcmwhfy: It could be because you are using the universal selector (i.e. *). If you want your cursor to appear no matter where it is on the page, it would be better if you used the body selector.

  • 13 VAT // Mar 7, 2013 at 11:57 am

    Is it possible to save a cursor using ANY automated client script ??

    Well, I’m working on some security implementation using Custom Cursor Images.
    I want to know whether it is possible to save a cursor being displayed over an HTML Division using some client script without accessing the server and on the same client machine where the HTML page has been rendered ??

  • 14 zoltan // Mar 7, 2013 at 6:53 pm

    @VAT: It is possible. Let’s say you have this DOM node you want to find the cursor for:

    <div id="mydiv"></div>
    

    It’s trivial to find the URL of the cursor with this JavaScript snippet:

    function getCurrentStyle(obj)
    {
      var computedStyle;
      if (typeof obj.currentStyle != 'undefined')
        { computedStyle = obj.currentStyle; }
      else
        { computedStyle = document.defaultView.getComputedStyle(obj, null); }
    
      return computedStyle;
    }
    
    getCurrentStyle(document.getElementById('mydiv')).cursor
    

    What security implications you are thinking of? I would love to know your thoughts.

  • 15 VAT // Mar 8, 2013 at 1:46 pm

    Thanks @zoltan.. but I just couldn’t understand your code. What is it doing ?? Could you please explain ?
    Actually I’m working on an Image CAPTCHA scheme that utilizes images from mouse cursors which would never remain same (dynamically generated). Like, for every NEW reference/retrieval made for the cursor with a URL would result in a new cursor.
    So is it possible for a hacker to copy and analyze that image (the one which is currently cursor on the DIV) ??

Give Feedback

Don't be shy! Give feedback and join the discussion.

Please Note: If you are asking for help using the information on this page or if you are reporting a bug with the code featured here, please include a URL that shows the problem you are experiencing along with the browser/version number/operating system combination where the issue manifests itself. Without this information, I may not be able to respond.

An orange star denotes a required field.