发生拖动时,浏览器会默认从拖动目标(dragstart事件触发的元素)生成半透明图像,并在拖动过程中跟随鼠标指针。这个图片是自动创建的,你不需要自己去创建它。然而,如果想要设置为自定义图像,那么 DataTransfer.setDragImage(img|element,xOffset,yOffset) 方法就能派上用场。
使用时需注意:1、setDragImage传入的第一个值img或者Element必须是页面中已经存在的元素,不然设置了也是白设置,然而设置不成功时浏览器将使用默认的拖动图片(所以自定义图像时DOM元素已经生成才可以)
2. 当拖拽父节点时,拖拽图片展示的是父节点和子节点的数据及整个节点; 如果想让只展示父节点数据(忽略子节点),则使用e.DataTransfer.setDragImage(e.target.children[0],0,0) 将第一个参数设置为子元素即可
应用: setDragImage对于任何非平凡的拖放用例都是IMO的重要特性.例如,考虑多选列表,其中拖动需要包括所有选定的项而不仅仅是拖动手势所在的行(简称多选拖拽), 下面就是项目中实现的效果。
多选拖拽的样式效果:
项目中是使用canvas绘制的, 然后转成图片进行显示, 下面是canvas的DOM节点:
<canvas id="myCanvas" width="275" height="160"></canvas>
列举一些canvas的设置:
const [transferTotalHeight, setTransferTotalHeight] = useState(0); // 拖拽图像总高度
const handleDragImage = (data: any[]) => {
let canvas: canvasProps | null;
let ctx: canvasProps;
canvas = document.querySelector('#myRightCanvas');
if (!canvas?.getContext) return;
ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 275, rightTransferTotalHeight); // 清除上次图像(重要, 不然容易产生
bug)
// 计算
let totalHeight = 5 * 32; // 设置最多显示5行
setRTransferTotalHeight(totalHeight);
// 整体填充颜色
ctx.fillStyle = '#f5f5f5';
// 整体填充位置及大小 x-横坐标 y-纵坐标 w-宽 h-高
ctx.fillRect(0, 0, 275, totalHeight);
// 整体描边颜色
// ctx.strokeStyle = '#333';
// // 整体描边位置及大小 x-横坐标 y-纵坐标 w-宽 h-高
// ctx.strokeRect(0, 0, 275, totalHeight);
let checkboxHeight = 8; // 初始复选框高度
let titleHeight = 20; // 初始整体高度
for (let i = 0; i < data.length; i++) {
if (i > 0) {
checkboxHeight += 32;
titleHeight += 32;
}
// 如果选择数据超过5条,设置为省略号
if (transferData.length > 5 && i >= 4) {
ctx.beginPath(); // 起始一条路径,或重置当前路径
ctx.arc(40, 144, 6, 0, Math.PI * 2, true); // 创建弧/曲线
ctx.closePath(); // 创建从当前点回到起始点的路径
ctx.fillStyle = 'rgb(0,0,0)'; // 设置或返回用于填充绘画的颜色、渐变或模式
ctx.fill(); // 填充当前绘图(路径)
ctx.beginPath(); // 起始一条路径,或重置当前路径
ctx.arc(80, 144, 6, 0, Math.PI * 2, true); // 创建弧/曲线
ctx.closePath(); // 创建从当前点回到起始点的路径
ctx.fillStyle = 'rgb(0,0,0)'; // 设置或返回用于填充绘画的颜色、渐变或模式
ctx.fill();
ctx.beginPath(); // 起始一条路径,或重置当前路径
ctx.arc(120, 144, 6, 0, Math.PI * 2, true); // 创建弧/曲线
ctx.closePath(); // 创建从当前点回到起始点的路径
ctx.fillStyle = 'rgb(0,0,0)'; // 设置或返回用于填充绘画的颜色、渐变或模式
ctx.fill();
break;
}
// 复选框填充及描边
ctx.strokeStyle = '#333';
ctx.lineWidth = 1; // 线宽
ctx.fillStyle = '#1890ff';
ctx.fillRect(8, checkboxHeight, 16, 16);
// 对勾
ctx.strokeStyle = '#eee';
ctx.moveTo(12, checkboxHeight + 8);
ctx.lineTo(16, checkboxHeight + 12);
ctx.lineTo(16, checkboxHeight + 12);
ctx.lineTo(20, checkboxHeight + 4);
ctx.stroke();
// ctx.rotate(0.5 * Math.PI);
//文字对齐方式 start、end、center、left、right(选填)
ctx.textAlign = 'start';
//设置字体和尺寸 (必填)
ctx.font = '400 12px PingFangSC-Regular';
//填充文字 x-横坐标 y-纵坐标 (必填)
ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillText(transferData[i].title, 32, titleHeight);
}
let img = new Image();
let imgdata = canvas.toDataURL('image/png');
let newdata = imgdata.replace(/^data:image\/png/, 'data:image/jpeg');
img.src = newdata;
}
handleDragImage(dataList);
// dragStart事件
const handleDragStart = () => {
....
e.dataTransfer.setDragImage(img, e.clientX - 380, 16);
...
}
踩了好多坑, 必须得记录一下...