canvas 之 DataTransfer.setDragImage() 解析

1,424 阅读3分钟

最近在使用canvs做布局图的设计,在使用到 DataTransfer.setDragImage() 的时候,查询了MDN和百度后,没有详细的解析,而且是有错误的情况,特结合代码和实际用例,给出合理的解决方案。

语法

void dataTransfer.setDragImage(img, xOffset, yOffset);
  • img |  Element

    用于拖曳反馈图像的图像 Element 元素。

如果 Element 是一个 img 元素,则将拖动位图设置为该元素的图像(保持大小);否则,将拖动数位图设置为从给定元素所生成的图片

  • xOffset

    使用 long 指示相对于图片的横向偏移量

  • yOffset

    使用 long 指示相对于图片的纵向偏移量

解析

  • 发生拖动时,从拖动目标 (dragstart事件触发的元素) 生成半透明图像,并在拖动过程中跟随鼠标指针。这个图片是自动创建的,你不需要自己去创建它。然而,如果想要设置为自定义图像,那么 DataTransfer.setDragImage()  方法就能派上用场。
  • 图像通常是一个 <image> 元素,但也可以是<canvas> 或任何其他图像元素。该方法的 x 和 y 坐标是图像应该相对于鼠标指针出现的偏移量。 坐标指定鼠标指针相对于图片的偏移量。例如,要使图像居中,请使用图像宽度和高度的一半。通常在 dragstart 事件处理程序中调用此方法。

实际用例

  • setDragImage 的第一个参数接受的是一个Element参数,这样的话,普通的html元素、image元素、canvas都可以传递。
  1. 以官网例子为例,把canvas作为参数传递,我首先尝试的是这种方式,发现并不能生效。(官方的例子没有运行成功)
function dragWithCustomImage(event) {
  var canvas = document.createElementNS("http://www.w3.org/1999/xhtml","canvas");
  canvas.width = canvas.height = 50;

  var ctx = canvas.getContext("2d");
  ctx.lineWidth = 4;
  ctx.moveTo(0, 0);
  ctx.lineTo(50, 50);
  ctx.moveTo(0, 50);
  ctx.lineTo(50, 0);
  ctx.stroke();

  var dt = event.dataTransfer;
  dt.setData('text/plain', 'Data to Drag');
  dt.setDragImage(canvas, 25, 25);
}

2.根据案例,我接着使用HtmlDivElement作为参数传递,创建了DIV元素,此时也没有生效

export function drawDragImage(dataTransfer: DataTransfer, context: string) {
  const drawItem: API.EquipmentInfo = JSON.parse(context);
  const div = document.createElement('div');
  div.style.height = itemObj.height + 'px';
  div.style.width = itemObj.width + 'px';
  div.style.border = '1px solid #000';
  const span = document.createElement('span');
  span.innerText = '2222';
  div.appendChild(span);
  dataTransfer.setDragImage(div, drawItem.width / 2, drawItem.height / 2);
}

3.然后,我改进了canvas,把canvas转化为图片,第一次拖拽的时候,因为image加载元素是异步导致了没有生效,如图1;第二以后拖拽的时候可以生效,如图二。

  const imageContent = canvas.toDataURL('image/jpeg', 1);
  const image = new Image();
  image.src = imageContent;
  image.onload = () => {
    console.log('image2 load');
  };
  dataTransfer.setDragImage(image, drawItem.width / 2, drawItem.height / 2);

image.png 图1 image.png 图2

4.最后我尝试了使用官网的方法,同样因为image加载图片是异步的,而拖拽事件是同步发生的,导致了第一次执行失败。
function dragstart_handler(ev) {
 console.log("dragStart");
 // 设置拖动的格式和数据。使用事件目标的 id 作为数据
 ev.dataTransfer.setData("text/plain", ev.target.id);
 // 创建一个图像并且使用它作为拖动图像
 // 请注意:改变 "example.gif" 为一个已经存在的图片
 // 或者,一个还没有创建出来的图片,那么浏览器将会使用默认的拖动图片
 // 译者注:默认的拖动图片与拖动对象没有联系。一般是一个小型文件图标
 var img = new Image();
 img.src = 'example.gif';
 ev.dataTransfer.setDragImage(img, 10, 10);
}

解决方案

在尝试了不同方式设置拖拽反馈图像,总结了一些解决方案:

  • 以html页面的元素为模版,动态生成内容,然后设置Element元素参数,可以设置DIV元素的z-index,隐藏在实际页面之下:这样可以动态生成要拖拽的元素,并和生成的fabric的group保持一致。完美的解决了问题。
export function drawDragImage(dataTransfer: DataTransfer, context: string) {
  const drawItem: API.EquipmentInfo = JSON.parse(context);
  const dragElement = document.getElementById('dragItem');
  const idElement = dragElement?.getElementsByClassName('dragItemId')[0];
  const nameElement = dragElement?.getElementsByClassName('dragItemName')[0];
  if (idElement) {
    idElement.innerHTML = drawItem.id;
  }
  if (nameElement) {
    nameElement.innerHTML = drawItem.typeName || '';
  }
  if (dragElement) {
    dragElement.style.height = drawItem.height + 'px';
    dragElement.style.width = drawItem.width + 'px';
    dragElement.style.border = '1px solid #000';
    dragElement.style.background = '#fff';
  }
  if (dragElement) {
    dataTransfer.setDragImage(dragElement, drawItem.width / 2, drawItem.height / 2);
  }
 }

image.png