New dragdrop recipe: donut dragdrop

A look at two current dragdrop recipies' pros and cons. A new recipe (the donut recipe) that might be better, faster and simpler in many situations.

In most JavaScript dragdrop implementations, when dragging an html element, the element generally remains under the cursor. This gives the same user experience as a desktop computer usually does when working with most dragdrop interfaces. Implementing this type of dragdrop user experience in a browser when targets are involved brings up a major problem that must be overcome. Since the dragged element is always under the cursor, the browser does not detect a "mouseover" event when the cursor and dragged element enters a target element. This is because the cursor is directly over the DOM branch containing the dragged element. The cursor is not over the branch containing the target. I have seen two dragdrop recipes have been tried to overcome this problem.

Looping recipe

The most common recipe is used in at least both Yahoo! UI dragdrop.js v0.10 and in Scriptaculous dragdrop.js 1.6. Simplified, while an item is dragged, after every "mousemove" event, the JavaScript loops through many targets to determine which targets are under event's cursor location. This is much looping and comparing cursor locations with target regions. The situation can be improved by caching target regions at the beginning of the drag but the looping and comparing still must happen with every mousemove. With many target regions this is very slow. Another problem with this recipe is overlapping and nested targets. Neither library listed does this well and usually the first target found is designated at the current target. The order the targets are searched is arbitrary so first found may not mean much. In many (most?) applications it would be best if the targets react in the same order they appear to the user in the browser. This means a target that appears on top of another is favored over the target behind.

Hidden layer recipe

The second recipe I know about avoids looping on every mousemove event. I'm told this is in the book DHTML Utopia but I did discover this by accident one day. In this recipe, when the page is loaded or a new target is added, a new hidden element is created at a very high CSS z-index for each target element. The hidden element has the same location and dimensions as the visible target element. Browser event listeners are attached to this hidden element. The dragged element is dragged at a lower z-index than the hidden elements (ie. between the hidden element and the visible target elements.) When the cursor and dragged element enter a target, the cursor is directly over the hidden element so the listeners fire. This is great and avoids looping with each mousemove but there are problems. It takes time to check size and location of each hidden element at the beginning of each drag. This time could be substantial for targets deep in the DOM tree. A problem still exists for nested and overlapping targets. It is complicated to create and manage the hidden elements in the same stack order as the corresponding visual target elements appear to the user.

My donut recipe

The main problem with the looping recipe is the dragged element is in the way and browser events cannot trigger when the cursor enters a target region. The hidden layer recipe avoids this problem but requires the creation of a complicated hidden layer when the drag starts. I don't want to loop. I do want to use browser events but I don't want to create the hidden layer. I haven't seen my solution elsewhere so this could be new.

For my donut recipe, when a drag is initiated a drag proxy is created from four divs. This looks like dragging an empty, square div with a border centered under the cursor. However each of the four divs is used to create one side of the square. This composite proxy is like a donut centered under the cursor. However none of the composite proxy donut is actually under the cursor. This means I can attach listeners to the actual target elements at the beginning of the drag. No looping with each mousemove and no hidden layer! Also the targets are naturally stacked as they appear to the user so nested and overlapping targets are no problem.

creative donut appearance

The donut recipe uses a composite proxy because a hole in the proxy must be under the cursor. This doesn't mean the proxy must appear to be a boring square border. A boring square will be fast but there are other options. The donut proxy "border width" can be made so big that the donut hole is only a pixel or four or nine etc. The four divs can be filled creatively with images or clones of what is being dragged. Some clever CSS using the overflow property could make the donut look almost exactly like whatever the user wishes to drag.

You could clone the original elements to the donut and hide the originals. Then the user wouldn't even know they are dragging a proxy except for the small hole inside that is only visible sometimes. Often the cursor will actually cover this hole.

The hole could be closer to one edge or corner of the proxy by having the four divs with different sizes and shapes. As long as the hole is under the cursor the donut idea will work.

Also you don't have to use all four divs to make the composite proxy. You could use zero, one or ten. As long as nothing is directly under the cursor during the drag so the browser can detect the events as the cursor moves over and out of the targets.

donut example

This is development code for a dragdrop library that will be capable of all the things I need (multiselect, strange shape draggables and targets, etc.) This shows the nested and overlapping targets working for Safari 1.3 and Firefox 1.5 on Mac and IE 6 and Firefox 1.5 on Windows XP.

donut example

The code is currently copyrighted.

Summary

The donut recipe solves many problems with current dragdrop recipies. The tradeoff is some minimal inconvenience in creating proxy with acceptable appearance. Situations without targets don't need the donut method. Some situations may not be slow with the looping recipe and may not have nested or overlapping targets. Some or many situations with many targets may benefit greatly from the donut recipe.

Update (Aug 10, 2006)

Please see my Donut DragDrop Release article for a release version you can use.

Comments

Have something to write? Comment on this article.

Nige June 16, 2006

Very nice idea, it will obviously have performance benefits when there are a lot of targets.

You are restricted to an outline though rather than dragging a partly opaque simulacrum of the dragged element(s) which is what users find intuitive - you can see what you are doing.

Where performance is seriously an issue, this would probably be acceptable.

Peter Michaux June 16, 2006

Nige,

Actually you are not restricted to an outline. The donut proxy appearance is only restricted by your imagination. By filling the four donut div's cleverly with html, you could make the donut look exactly like the dragged elements with a one pixel hole in the middle.

On a slow computer, when the the hole is extremely small, there are some implementation issues with flicker but I haven't looked for solutions to this yet. I imagine something clever can be done.

Joe June 16, 2006

Does the hole actually have to be in the middle or could you construct the four divs so that the 1pixle hole was in the same relative position as the cursor when the user started the drag? If you could construct the divs in this fashion then you could probably make the doughnut look exactly like the originial item.

Alex June 16, 2006

This is actually really slow for me. I'm on Firefox 1.5.0.3 on Gentoo GNU/Linux. The selected item trails behind my cursor very slowly. Slow in Konqueror (3.4) as well. I'm on a system with 512mb of ram and an AMD Athlon XP+ 2200. I don't know what other information I can give you, other than to say that all of the YUI drag and drop examples run a lot faster.

Sounds like some interesting work though.

Peter Michaux June 16, 2006

Joe,

The hole must be under the cursor during the drag. That is the main requirement.

The four divs can be different sizes and shapes so the donut could look like is it not centred under the cursor.

Peter Michaux June 16, 2006

Alex,

Very interesting that you find this example slower than the other Yahoo! examples. I find that all the Yahoo! dragdrop examples run very slowly for me and this example is similarly slow. I just finished an article about Yahoo! dragdrop v0.10 performance for why this might be true.

Actually, the donut example only has a few targets and the donut idea will show performance gains when there are many targets.

The biggest plus for me is that nested and overlapping targets are handled well with the donut recipe.

Alex June 16, 2006

Yes, it does seem weird. The funny thing is just how slow it is (I'm not trying to be rude here, just descriptive) - it's painful, like dragging over a slow vnc connection.

It actually doesn't seem to be the case that the Yahoo examples run quickly for me. Perhaps it's a performance regression since the last release, or just that my firefox has a lot of tabs open. Drag test is slow, though still a little faster than your doughnut example, DD swap is noticeably faster, sortable list if a little unresponsive, but not bad for a list of that size, the targetable affordance example seems to be really rather zippy, grid is slow, and so is resize. Your example without SetXY on your recent blog post is nice and speedy.

Are you seeing good performance in the 'targetable affordance' example?

Peter June 16, 2006

Alex,

All the Yahoo! examples run at the same speed for me: slow. The dragged item only actually moves when I stop moving the cursor.

Julian Turner June 28, 2006

Great idea. Have you tried creating the proxy pieces using the "clip" property instead?

Peter June 29, 2006

Julian,

I haven't tried the clip property. I don't know if clip property is reliable enough yet. In Eric Meyer's 2004 Cascading Style Sheets: The Definitive Guide he writes, "...in current browsers, [clip] acts in inconsistent ways and cannot be relied upon in any cross-browser environment." The overflow:hidden property has worked very well in my research examples.

Julian Turner June 30, 2006

I take your point. If overflow works, then stick with it.

Perhaps things have moved on since 2004, and perhaps your ideas have given a lease of life for the clip property.

I have tried the basics of creating your donut using clip in IE 6, Firefox 1.5, and Opera 9. All three seem to operate consistently. I have not implemented a full dragging operation yet however, so I have no doubt subtle quirks will soon begin to manifest themselves when I actually try to get it to work.

A potential advantage of clip (and I have not studied your code in ddproxy.js too closely) is that you can set the same top and left for each piece. I.e. clip operates "relative" to the same top/left values. So it may save on calculations.

I plan to try to complete an example "clip" based implementation and will let you know if it works.

Regards, Julian

Julian Turner July 1, 2006

Have been testing. Works fine with IE6 and Firefox.

Unfortunately, clip in Opera only shows the element through the "hole", but does not register mouseover events for it. So clip fails because of Opera.

Regards, Julian

Peter Michaux July 1, 2006

Julian,

Thanks for the information about clip.

Mark July 7, 2006

I'm using YUI .10 with a lot of draggable elements. It's moving painfully slow and I'm in desperate need of a solution. Is the source code available?

Peter July 7, 2006

Mark,

If you've looked in my code it isn't really finished. It's a bit hacky still and I don't know what I'll end up doing with it yet. I don't have time to finish it now or manage this as an open source project. Unfortunately, I haven't even thought about this for a week or so.

If you search in the YUI recent posts you will see a lot of discussion about this and Tim has some ideas that might take my idea even further by combining the donut idea with a dragdrop manager. It might not be able to do all the things my code can do but those extras are reasonable rare (clusters of elements as a single element, external handles).

Mark July 7, 2006

Ahh, darn. Very clever concept. You've elaborated enough on the idea that I could attempt to code something similar into the existing YUI DD objects if I can find some extra time.

I'll search the YUI discussion as you suggest for additional ideas.

:-)

Have something to write? Comment on this article.