16
votes

In my web application I try to implement some drag and drop functionality. I have a global JavaScript component which does the the basic stuff. This object is also responsible for changing the mouse cursor, depending of the current drag operation (move, copy, link). On my web page there are various HTML elements which define an own cursor style, either inline or via a CSS file.

So, is there a way for my central drag and drop component to change the mouse cursor globally, independent from the style of the element the mouse cursor is over?

I tried:

document.body.style.cursor = "move"

and

document.body.style.cursor = "move !important"

But it doesn't work. Every time I drag over an element which defines a cursor style, the cursor changes to that style.

Sure, I could change the style of the element I'm currently dragging over, but then I have to reset it when I leave the element. This seems a little bit to complicated. I'm looking for a global solution.

6

6 Answers

15
votes

Please: don't massacre your CSS!

To implement a drag and drop functionality, you have to use a very important API: element.setCapture(), which does the following :

  • All mouse events are redirected to the target element of the capture, regardless of where they occured (even outside the browser window)
  • The cursor will be the cursor of the target element of the capture, regardless where the mouse pointer is.

You have to call element.releaseCapture() or document.releaseCapture() to switch back to normal mode at the end of the operation.

Beware of a naïve implementation of drag and drop: you can have a lot of painful issues, like for example (among others): what happens if the mouse is released outside the browser's window, or over an element which has a handler that stops propagation. Using setCapture() solves all this issues, and the cursor style as well.

You can read this excellent tutorial that explains this in detail if you want to implement the drag and drop yourself.

Maybe you could also use jQuery UI draggable if possible in your context.

6
votes
document.body.style.cursor = "move"

should work just fine.

However, I recommend to do the global styling via CSS.

define the following:

body{
   cursor:move;
}

The problem is, that the defined cursors on the other elements override the body style.

You could do someting like this:

your-element.style.cursor = "inherit"; // (or "default")

to reset it to the inherited style from the body or with CSS:

body *{
   cursor:inherit;
}

Note however, that * is normally considered a bad selector-choice.

4
votes

Unfortunately element.setCapture() does not work for IE

I use a brute force approach - open a transparent div on top of entire page for the duration of drag-drop.

.tbFiller {
   position:absolute;
   z-index:5000; 
   left:0;
   top:0;
   width:100%;
   height:100%; 
   background-color:transparent;
   cursor:move;
}

...

function dragStart(event) {
    // other code...
    document.tbFiller=document.createElement("div");
    document.tbFiller.className="tbFiller"
}

function dragStop(event) {
    // other code...
    document.tbFiller.parentNode.removeChild(document.tbFiller);
}
1
votes

This is what I do and it works in Firefox, Chrome, Edge and IE as of 2017.

Add this CSS rule to your page:

html.reset-all-cursors *
{
    cursor: inherit !important;
}

When the <html> element has the "reset-all-cursors" class, this overrides all cursors that are set for elements individually in their style attribute – without actually manipulating the elements themselves. No need to clean up all over the place.

Then when you want to override your cursor on the entire page with that of any element, e. g. the element being dragged, do this in JavaScript:

element.setCapture && element.setCapture();
$("html").addClass("reset-all-cursors");
document.documentElement.style.setProperty("cursor", $(element).css("cursor"), "important");

It uses the setCapture function where it is available. This is currently just Firefox although they say it's a Microsoft API. Then the special class is added to the entire document, which disables all custom cursors. Finally set the cursor you want on the document so it should appear everywhere.

In combination with capturing events, this may even extend the dragging cursor to outside of the page and the browser window. setCapture does this reliably in Firefox. But elsewhere it doesn't work every time, depending on the browser, its UI layout, and the path along which the mouse cursor leaves the window. ;-)

When you're finished, clean up:

element.releaseCapture && element.releaseCapture();
$("html").removeClass("reset-all-cursors");
document.documentElement.style.setProperty("cursor", "");

This includes jQuery for addClass and removeClass. In simple scenarios you could just plain compare and set the class attribute of document.documentElement. This will break other libraries like Modernizr though. You can get rid of the css function if you know the element's desired cursor already, or try something like element.style.cursor.

1
votes

Thanks to some of the other answers here for clues, this works well:

/* Disables all cursor overrides when body has this class. */
body.inheritCursors * {
    cursor: inherit !important;
}

Note: I didn't need to use <html> and document.documentElement; instead, <body> and document.body work just fine:

document.body.classList.add('inheritCursors');

This causes all descendant elements of <body> (since it now has this inheritCursors class) to inherit their cursor from <body> itself, which is whatever you set it to:

document.body.style.cursor = 'progress';

Then to yield back control to the descendant elements, simply remove the class:

document.body.classList.remove('inheritCursors');

and to unset the cursor on the <body> to the default do:

document.body.style.cursor = 'unset';
0
votes

I tried using setPointerCapture which worked great. The downside is, that (of cause) all pointer events will not work as before. So I lost hover styles etc.

My solution now is pretty straight forward and for my usecase better suited then the above CSS solutions.

To set the cursor, I add a new stylesheet to head:

const cursorStyle = document.createElement('style');
cursorStyle.innerHTML = '*{cursor: grabbing!important;}';
cursorStyle.id = 'cursor-style';
document.head.appendChild(cursorStyle);

To reset it, I simply remove the stylesheet:

document.getElementById('cursor-style').remove();