自定义光标,无需使用库即可拖放HTML元素

32
我有一个HTML页面,其中有一些可拖动的元素。我们的规格说明说,在悬停鼠标在这样的元素上时,光标必须是grabgrab,并且在拖动期间,光标必须是grabbinggrabbing
我知道可以设置dropEffect来更改在放置区域上方的光标外观,但选项很少:copymovelinknone——没有自定义或类似选项。
我尝试使用Javascript和CSS更改光标,例如在触发ondragstart时设置cursor: grabbing;。但是,在放置区域上拖动时,浏览器默认的移动光标出现了。

问题是:我错过了什么,无法在拖动期间显示抓取光标(grabbing)?

不幸的是,我不能在解决方案中使用JQuery或其他辅助库。谢谢!

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>


你会使用纯JavaScript还是也会用到jQuery呢?如果你会使用jQuery,那么你会使用jQuery UIDraggable插件吗? - UfguFugullu
纯JS。抱歉,JQuery无法使用。 - Travenin
你将不得不使用一个假光标;一个跟随鼠标移动的图像。这将显示原生图标,因此你需要使其与其外观相配而不是竞争。据我所知,这是最接近你能得到的。 - dandavis
7个回答

16

看起来浏览器不允许在拖放操作开始时更改光标。我不知道为什么,但这是一个已知的问题,我相信他们将来会解决。

如果不能使用jQuery,一个可能的解决方法是从头开始实现拖放,使用鼠标事件并克隆源元素:

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>


看起来浏览器在本机拖动事件中不支持自定义光标,这很遗憾。你的回答是我预期的最接近的。谢谢。 - Travenin

8
这是一个已知的问题,报告在这里
在拖动时,光标会自动变为正常状态。我的尝试得到了以下结果:在带有抓取光标的元素上添加active。当它处于活动状态时,光标会改变,但一旦开始拖动,它就会自动改变。
我尝试在dragstart事件中将body光标设置为grabbing,但没有结果。甚至它也不起作用。

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>


4

我为了弄清楚这个问题花费了很大的精力。被接受的答案曾经是网络上最好的答案,但现在的最佳实践是使用元素的.setPointerCapture事件,它允许您监听和响应元素上的拖动行为而不会被限制到Drag API的狭窄行为中。其中一种方法如下:

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)
}

明显的好处是这里没有光标劫持悄悄溜进来的问题。

这个解决方案帮助我在拖动自定义进度条上的标记时隐藏了光标,使其看起来像是被"阻塞"了。就像音量调节或当前播放曲目的时间变化一样。我想知道这个解决方案是否存在任何潜在问题。非常感谢! - M. Çağlar TUFAN

1
我对使用纯 JavaScript 实现可拖动元素的知识略有了解,但很抱歉我无法解释以下内容。

问题在于 onDragEnd 事件从未被触发,所以我搜索了一些内容,并找到了这个 example 可拖动元素的示例。
现在,如果您更改 onDragStart 事件的函数,它将起作用,但我认为您需要以另一种方式更改光标,例如更改 onDragStart 中 body 的类。
var onDragStart = function(event) {
  event.dataTransfer.setData("Text", event.target.id);
  event.currentTarget.classList.add("being-dragged");
};

全能型

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>


1
谢谢你的回答,它为我的代码片段提供了一个修复bug的方法(我编辑了我的问题),但我的真正问题是如何抓取光标,而你的回答并没有提供这方面的解决方案。 - Travenin

0
我通过使用mouseover事件来定义光标,然后稍微移动移动坐标以使鼠标指针保持在元素内部来解决了这个问题。 这里有演示 在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();
};

-1

试一下吧!对我有效!

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

.draggable:active {
    cursor: -webkit-grabbing;
    cursor: grabbing;
}

对我没用。你使用的是哪个浏览器和操作系统?我在Windows上,使用最新的Chrome和Firefox。 - Travenin
我使用Windows 8和Chrome 59,对我来说没有问题。 - Richard Foucaud
在这个 JS fiddle 中吗?https://jsfiddle.net/7Lrofezt/ 我将您的答案与我的代码结合起来了。 - Travenin
好的,我明白了。当你移动框时,光标会发生变化。我尝试修改你的jsfiddle来帮助你解决这个问题,但我还不能。祝你好运。 - Richard Foucaud

-1

我花了一些时间找到这个解决方案,最终采用了这个技巧。我觉得这是最好的方法,代码更少,工作更适合。

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

}

.drag:active {
    cursor: url('../images/grabbing.png'), auto;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接