Determining Offsets with Scrolling Overflow
In drag and drop situations, there's often a time where an area of the page is within an element with the overflow set to scroll. This is common to display a list of items that would be longer than the rest of the elements on the page. If you're dragging from Section A to Section B, you don't want the user to have to scroll down the page to access the drop point. JPG Magazine is a prime example of that (see Figure 1).
Figure 1. The scroll area from JPG Magazine.
With JPG Magazine, the elements can be dragged from the left and dropped within the list on the right. The list of "themes" is quite long and is therefore placed within a scrollable section using overflow:scroll
.
When the user drags the image over, the site needs to accurately know which theme you happen to be dropping the photo on. As described in Determining the Droppable, this is done by calculating the offsetTop
and offsetLeft
of the elements all the way up the tree (see Quirksmode for an example script). We have to go all the way up the tree because the offsets are only relative to the positioned parent element.
In the example, the "Fluid" element might have an offsetTop of 200px which would be 200 pixels from the top of the element that has overflow:scroll
set on it. It does not take into account the fact that the parent element has been scrolled. Therefore, the cursor's position which is relative to the offset of the in relation to the total offset of the "Fluid" element won't match.
scrollTop and scrollLeft
If a parent element has been scrolled, you can use the scrolled elements's scrollTop
and scrollLeft
properties. Therefore, to calculate an element's real position, you need to total up the total offsets and take away any scrolling offsets. Whether you have to determine whether the page has scrolled or not depends on whether you can determine whether the mouse or element position is relative to the top-left of the document or to the window.
JavaScript Libraries
JavaScript libraries may or may not directly take this into account when trying to determine the position of an element. jQuery (using offset
) and YUI (using getXY
) do this automatically whereas Mootools and PrototypeJS require more explicit direction to include the offset.
Mootools requires you to pass in an array of elements that may have scrolling when using getPosition
. PrototypeJS has realOffset
to calculate the scroll offset separately.
For a look in the drag and drop process, check out:
Conversation
An interesting topic to explore. I played with the Prototype library briefly, but never tried dropping within an element with the overflow:scroll rule. Now I'm sort of curious how modifying the z-index affects layering upon dragging in/out of a scrollable element or performing the scrolling action.
Great post, I also use Prototype and it has some useful functions for drag and drop but I haven't used them enough to actually get a good feel for for how well they work in situations such as this.
I do like it's method for calling in external files and passing variables to them though, the library keeps it simple and sweet.
@Brendan: normally when dragging elements, a proxy element is inserted as a child of the body and therefore you can avoid the z-indexing issues to a degree (you still have to make sure the element has a higher z-index than anything else, though). Calculating that element in relation to the droppable is where the scrollTop/scrollLeft values comes into play. Dragging an element or creating a proxy within any element with overflow (scroll or hidden) is a recipe for disaster.
In regards to scrolling areas, some libraries can auto-scroll if you're close to the top or bottom of the scrollable area. I know Scriptaculous can do this but I'm not sure if the base PrototypeJS can and whether any other JS libraries take this under consideration.
indeed an interesting topic to explore. Good to hear Jquery does it automagically, though I suppose something more explicit would be better for the programmer. Do you have any idea how Ext holds up here?
Have you seen this: http://peter.michaux.ca/article/51
Essentially he enables event delegation doing something different with the draggable shadow. He splits it into four pieces and arranges these around the mouse, leaving a small box under the mouse to allow events underneath to be triggered.