Custom cursor with drag and drop an HTML element without libraries
Asked Answered
W

7

32

I have an HTML page which has some draggable elements. Our specs say that hovering mouse on such element the cursor must be grab grab, and during drag cursor must be grabbing grabbing.

I know it is possible to set dropEffect which changes cursor appearance above drop zone, but there are very little options: copy, move, link, and none -- no custom or alike.

I have tried to change cursor with Javascript and CSS, like setting cursor: grabbing; when ondragstart is fired. But browser default move cursor appears instead when dragging on drop zone.

So the question is: What am I missing to show grabbing cursor (grabbing) during drag?

Unfortunately I cannot use JQuery or other helping libraries in the solution. Thanks in advance!

var onDragStart = function(event) {
    event.dataTransfer.setData("Text", event.target.id);
    event.currentTarget.classList.add("being-dragged");
};

var onDragEnd = function(event) {
    event.currentTarget.classList.remove("being-dragged");
};

var onDragOver = function(event) {
    event.preventDefault();
};
.dropzone {
    width: 500px;
    height: 200px;
    background-color: silver;
}

.block {
    position: absolute;
    background-color: pink;
    margin: 10px;
    border: 20px solid pink;
}

.draggable {
    cursor: -webkit-grab;
    cursor: grab;
}

.being-dragged {
    cursor: -webkit-grabbing;
    cursor: grabbing;
    background-color: red;
}
<div class      = "dropzone"
    ondragover  = "onDragOver(event);"
    >
    Grab and drag block around
    <div class      = "draggable block"
        draggable   = "true"
        ondragstart = "onDragStart(event);"
        ondragend   = "onDragEnd(event);"
        >
        I'm draggable
    </div>
</div>
Wikiup answered 8/6, 2017 at 23:49 Comment(3)
Should it be plain JavaScript or do you would use jQuery too? If you would use jQuery then would you use jQuery UIs Draggable Plugin?Figured
Plain JS. JQuery is unusable, sorry.Wikiup
you will have to use a fake cursor; an img that follows the mouse. this will be shown with the native icons, so you need to make it compliment that look instead of compete with it. afaik, that the closest you'll get.Inequitable
B
16

It seems that browsers don't allow changing the cursor at the beginning of a drag & drop operation. I don't know why but it's a known issue, I believe they will in the future.

If jQuery is not an option, a possible way around is to implement a drag & drop from scratch, using mouse events and cloning the source element:

var onDragStart = function (event) {
  event.preventDefault();
  var clone = event.target.cloneNode(true);
  clone.classList.add("dragging");
  event.target.parentNode.appendChild(clone);
  var style = getComputedStyle(clone);
  clone.drag = {
    x: (event.pageX||(event.clientX+document.body.scrollLeft)) - clone.offsetLeft + parseInt(style.marginLeft),
    y: (event.pageY||(event.clientY+document.body.scrollTop)) - clone.offsetTop + parseInt(style.marginTop),
    source: event.target
  };
};

var onDragMove = function (event) {
  if (!event.target.drag) {return;}
  event.target.style.left = ((event.pageX||(event.clientX+document.body.scrollLeft)) - event.target.drag.x) + "px";
  event.target.style.top = ((event.pageY||(event.clientY+document.body.scrollTop)) - event.target.drag.y) + "px";
};

var onDragEnd = function (event) {
  if (!event.target.drag) {return;}
  // Define persist true to let the source persist and drop the target, otherwise persist the target.
  var persist = true;
  if (persist || event.out) {
    event.target.parentNode.removeChild(event.target);
  } else {
    event.target.parentNode.removeChild(event.target.drag.source);
  }
  event.target.classList.remove("dragging");
  event.target.drag = null;
};

var onDragOver = function (event) {
  event.preventDefault();
};
.dropzone {
  width: 500px;
  height: 200px;
  background-color: silver;
}

.block {
  position: absolute;
  background-color: pink;
  margin: 10px;
  border: 20px solid pink;
}

.draggable {
  position: absolute;
  cursor: pointer; /* IE */
  cursor: -webkit-grab;
  cursor: grab;
}

.dragging {
  cursor: -webkit-grabbing;
  cursor: grabbing;
  background-color: red;
}
<div class="dropzone" onmouseover="onDragOver(event);">
  Grab and drag block around
  <div class    = "draggable block"
    onmousedown = "onDragStart(event);"
    onmousemove = "onDragMove(event);"
    onmouseup   = "onDragEnd(event);"
    onmouseout  = "event.out = true; onDragEnd(event);"
  >
    I'm draggable
  </div>
</div>
Baseless answered 18/6, 2017 at 2:28 Comment(1)
It really seems like browsers don't support custom cursors in native drag events, which is sad. Your answer is closest to what I was expecting. Thank you.Wikiup
T
8

It is a known issue reported here

While dragging, the cursor will automatically changed to normal.

My tries gave me the following. Gave an active on the element with grabbing cursor. While it is active, the cursor will change but once you start the drag, it will change automatically.

I tried to set body cursor to grabbing on dragstart but no result. Even it is not working.

var onDragStart = function(event) {
    event.dataTransfer.setData("Text", event.target.id);
    event.currentTarget.classList.add("being-dragged");
};

var onDragEnd = function(event) {
    event.currentTarget.classList.remove("being-dragged");
};

var onDragOver = function(event) {
    event.preventDefault();
};
.dropzone {
    width: 500px;
    height: 200px;
    background-color: silver;
}

.block {
    position: absolute;
    background-color: pink;
    margin: 10px;
    border: 20px solid pink;
}

.draggable {
    cursor: -webkit-grab;
    cursor: grab;
}

.draggable:active{
    cursor : -moz-grabbing;
    cursor: -webkit-grabbing;
    cursor: grabbing;
}
.being-dragged{
    background-color: red;
    cursor : -moz-grabbing;
    cursor: -webkit-grabbing;
    cursor: grabbing;
}
<div class      = "dropzone"
    ondragover  = "onDragOver(event);"
    >
    Grab and drag block around
    <div class      = "draggable block"
        draggable   = "true"
        ondragstart = "onDragStart(event);"
        ondragend   = "onDragEnd(event);"
        >
        I'm draggable
    </div>
</div>
Tokay answered 11/6, 2017 at 16:41 Comment(0)
K
4

I went through a lot of pain trying to figure this out. The accepted answer was the best answer on the web, but best practices now would be to use the element's .setPointerCapture event, which allows you to listen to and act upon drag like behaviors on an element without being boxed into the narrow behavior of the Drag API. One way to do it would be like so:

el.onpointerdown = ev => {
    el.onpointermove = pointerMove 
    el.setPointerCapture(ev.pointerId)
}

pointerMove = ev => {
    console.log('Dragged!')
}

el.onpointerup = ev => {
    el.onpointermove = null
    el.releasePointerCapture(ev.pointerId)
}

The obvious gift being the fact that there is no cursor hijacking to be found sneaking in the backdoor here.

Kirmess answered 22/3, 2019 at 8:16 Comment(1)
This solution helped me hide cursor looking "blocked" while dragging the bullet on custom progress bar to change the progress bar's value. Like volume change or currently playing track's time. I wonder if there are any pitfalls to this solution. Thanks a lot!Birdella
F
1

I know just a little bit about draggable elements with pure JavaScript and I'm sorry that I can't explain the following.

The problem was that the onDragEnd never get fired so I've searched something and find this example with draggable elements.
Now, if you change the function of the onDragStart event it will work but I think you have to change the cursor in another way like to change the class of the body onDragStart

var onDragStart = function(event) {
  event.dataTransfer.setData("Text", event.target.id);
  event.currentTarget.classList.add("being-dragged");
};

All in one

var onDragStart = function(event) {
  event.dataTransfer.setData("Text", event.target.id);
  event.currentTarget.classList.add("being-dragged");
};
var onDragEnd = function(event) {
  event.currentTarget.classList.remove("being-dragged");
};
var onDragOver = function(event) {
  event.preventDefault();
};
.dropzone {
  width: 500px;
  height: 500px;
  background-color: silver;
}
.block {
  width: 200px;
  height: 50px;
  background-color: pink;
}
.draggable1 {
  cursor: -webkit-grab;
  cursor: grab;
}
.being-dragged {
  cursor: -webkit-grabbing;
  cursor: grabbing;
  background-color: red;
}
<div class="dropzone" ondragover="onDragOver(event);">
  <div class="draggable1 block" draggable="true" ondragstart="onDragStart(event);" ondragend="onDragEnd(event);">
    I'm draggable
  </div>
</div>
Figured answered 9/6, 2017 at 9:1 Comment(1)
Thank you for the answer, it provided a bugfix in my snippet (edited my question), but my real problem is grabbing cursor, and your answer is not providing that.Wikiup
T
0

I solved this by using the mouseover event to define the cursor, then shifting the move co-ords slightly to keep the mouse pointer inside the element. Demo here on JSFiddle

var onDragMove = function (e) {
const target = e.target;
  // keep mouse inside element during drag, so that cursor defined by 'mouseover' stays with it
  const x = e.clientX - 10;
  const y = e.clientY - 10;
  const moveFromLeft = x - target.parentElement.getBoundingClientRect().left; 
  const moveFromTop =  y - target.parentElement.getBoundingClientRect().top;; 

  target.style.left = `${moveFromLeft}px`
  target.style.top = `${moveFromTop}px`
};
var onMouseOver = function(e) {
        e.target.style.cursor = 'grab'
};
var  onMouseOut = function(e) {
        e.target.style.cursor = 'default'
};
var  onMouseUp = function(e) {
        e.target.style.cursor = 'default'
};
var onDragOver = function (e) {
  e.preventDefault();
};
Taluk answered 13/4, 2023 at 16:1 Comment(0)
S
-1

Try this ! It works for me !

.draggable {
    cursor: -webkit-grab;
    cursor: grab;
}

.draggable:active {
    cursor: -webkit-grabbing;
    cursor: grabbing;
}
Sappy answered 12/6, 2017 at 9:52 Comment(4)
Not working for me. Which browser and OS are you using? I'm on Windows and latest Chrome and Firefox.Wikiup
I'm on Windows 8 and Chrome 59 and no problem for meSappy
In this JS fiddle too? jsfiddle.net/7Lrofezt I combined your answer to my code.Wikiup
Ok i see.. the cursor change when you are moving the box. I tried to modify your jsfiddle to help you solve this problem but i can't yet. Good luckSappy
D
-1

I spent sometime to find solution for this, ended with this trick. I feel this is best way less code and apt work.

.drag{
    cursor: url('../images/grab.png'), auto; 

}

.drag:active {
    cursor: url('../images/grabbing.png'), auto;
}
Dispart answered 14/6, 2017 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.