返回博客

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 改了名字,但默认行为不变。

坑:dragDropEnabled 默认 true,原生 handler 在 OS 层吞掉了 DOM 拖放事件

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-Ctauri 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/

无命中则安全。

正解:dragDropEnabled: false + 完整重启

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,且必须完整重启才生效。