Tauri v2 默认吞掉 HTML5 拖拽——dragDropEnabled 的隐形拦截
在 Tauri v2 桌面应用里做了一个可拖拽排序的侧边栏列表——draggable + onDragStart + onDragOver + onDrop 全套写完,在 vite dev 浏览器里拖拽排序丝滑流畅。把同一份代码跑进 Tauri 窗口,诡异的事情发生了:拖影正常浮起,松手,什么都没发生。列表纹丝不动。
现象
- 浏览器环境:拖拽排序完全正常,
dragover/drop事件如期触发。 - Tauri 窗口:拖影(drag ghost)浮起,鼠标移动过其他元素,松手,
onDrop回调从不触发。 - 控制台无报错,没有任何线索。
- 关键描述:能拖起来,但放不下去——不是完全无法拖拽,而是
dragover/drop两个事件在 Tauri 窗口内永远静音。
根因
Tauri v2 的窗口配置项 dragDropEnabled 默认值是 true。
当它为 true 时,Tauri 在操作系统层面为 webview 注册了一个文件拖放处理器(file-drop handler),把所有拖拽事件在原生层截获,转换成 Tauri 的 drag-drop 事件向 Rust 侧派发。这个拦截发生在 DOM 事件之前,结果就是 JS 侧的 dragover/drop 永远收不到。
Tauri v1 里这个配置叫 fileDropEnabled,升级到 v2 改了名字,但默认行为不变。
src-tauri/tauri.conf.json 里若没有显式写 dragDropEnabled,它就是 true。这个配置把拖拽事件在操作系统层面截走,转发给 Rust,JS 侧的 dragover/drop 完全收不到。症状是"能拖不能放",且浏览器里完全正常,极易误判为前端 bug 而反复排查 React 代码。
正解
第一步: 在每个需要页内拖拽的窗口配置里加一行:
// src-tauri/tauri.conf.json → app.windows[]
{
"label": "main",
"dragDropEnabled": false
}如果窗口是在 Rust 代码里用 WebviewWindowBuilder 创建的,改成调用:
.drag_drop_handler(false)第二步:完整重启应用。 dragDropEnabled 在窗口创建时生效,HMR 热更新和 webview reload 都不管用。必须 Ctrl-C 掉 tauri dev 进程,重新 tauri dev。
第三步(macOS 额外加固): WKWebView 要求 drag 数据非空才会触发 drop,务必在 onDragStart 里设置数据:
onDragStart={(e) => {
setDragId(id);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', id); // macOS WKWebView 必须
}}第四步(扩大投放区域): onDrop 要在 dragover 期间 preventDefault() 才能被触发,投放区应包裹整个卡片/行,而不仅仅是标题条:
<div
className="card"
onDragOver={(e) => {
if (dragId && dragId !== id) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
}}
onDrop={(e) => {
if (dragId && dragId !== id) {
e.preventDefault();
reorder(dragId, id);
}
}}
>
<div className="card-header" draggable onDragStart={...} onDragEnd={...}>
{/* 仅头部可拖起,但整个卡片都是投放区 */}
</div>
</div>修改前先确认安全: 禁用 dragDropEnabled 会让 OS 文件拖放到窗口的能力失效。跑一遍 grep 确认没有用到:
grep -rni "drag-drop\|dragdrop\|onDragDrop\|file-drop\|fileDrop\|tauri://drag" src/ src-tauri/src/无命中则安全。
在 tauri.conf.json 每个窗口下显式设 "dragDropEnabled": false,然后完整重启 tauri dev(非 reload)。macOS 额外在 onDragStart 里调 setData('text/plain', id),投放区覆盖整个可接收元素(不只是标题)。
实测对比
| 环境 | dragover 触发 | drop 触发 | 排序生效 |
|------|:---:|:---:|:---:|
| 浏览器 vite dev | 是 | 是 | 是 |
| Tauri 窗口(默认配置) | 否 | 否 | 否 |
| Tauri 窗口(dragDropEnabled: false + 重启) | 是 | 是 | 是 |
一句话外卖
Tauri v2 的
dragDropEnabled默认true会在 OS 层静默吞掉所有 DOM 拖放事件;页内 HTML5 DnD 只要不需要 OS 文件拖入,就应该显式设false,且必须完整重启才生效。