拖放 Drag and Drop
本教程共 42 篇 · 第 25 篇 · 更新于 2026-08-03
25. 拖放 Drag and Drop
本节目标
- 在 options 里开启文件拖放开关。
- 用
OnFileDrop在 Go 或前端拿到拖入的文件路径。 - 用 CSS 属性圈出”允许落文件”的区域并高亮。
25-1 先开开关
想让用户把文件拖进窗口,第一步不是写代码,而是开开关。
在 options.App 的 DragAndDrop 里,把 EnableFileDrop 设成 true:
DragAndDrop: &options.DragAndDrop{
EnableFileDrop: true,
},
不开这个,后面所有监听都收不到任何东西。
Note拖放能力是给”文件”用的,比如拖一张图片、一个压缩包进窗口。拖的是 HTML 元素那种网页内部拖拽,不属于这套机制,那是浏览器原生行为。
关掉开关后,运行时方法 OnFileDrop 才能用。也可以监听 wails:file-drop 事件,两种入口下面都讲。
25-2 Go 侧监听拖放
Go 侧用 runtime.OnFileDrop,回调里拿到三个东西:落点的 x、y 坐标,还有文件路径切片。
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
rt.OnFileDrop(ctx, func(x, y int, paths []string) {
for _, p := range paths {
fmt.Println("拖进来的文件:", p)
}
})
}
paths 是绝对路径。拿到之后怎么处理都行,比如读图、上传、解析。
不想监听了,调 OnFileDropOff 一次性清掉所有监听器:
func (a *App) StopDrop() {
rt.OnFileDropOff(a.ctx)
}
Tip拖进来的可能是文件夹,也可能一次拖多个。
paths是切片,循环处理才稳,别只取第一个。
25-3 前端监听拖放
前端同样有 OnFileDrop,但多一个参数 useDropTarget。
window.go.main.App.OnFileDrop(
(x: number, y: number, paths: string[]) => {
console.log("坐标", x, y);
console.log("文件", paths);
},
false
);
useDropTarget 为 false 时,只要文件落在窗口任意位置,回调就触发。
Warning前端这套
OnFileDrop是运行时方法,走的是绑定路径。要确保 Go 侧OnStartup已经拿到ctx,否则监听注册不上。
另一个纯前端的路子是监听事件,不依赖绑定:
import { EventsOn } from "@wailsio/runtime";
EventsOn("wails:file-drop", (data: { x: number; y: number; paths: string[] }) => {
console.log(data.paths);
});
wails:file-drop 事件带回的数据结构和回调一致,挑顺手的方式用。
25-4 指定拖放目标区域
有时候你不想整个窗口都能落文件,只想让某个框接收。
这时候把 useDropTarget 设成 true。Wails 会在鼠标移动时检查:当前悬停的元素有没有特定的 CSS 属性。有,就给它加一个 wails-drop-target-active 类。
那个”特定属性”由 CSSDropProperty 和 CSSDropValue 决定,默认是 --wails-drop-target 配 drop。
DragAndDrop: &options.DragAndDrop{
EnableFileDrop: true,
CSSDropProperty: "--wails-drop-target",
CSSDropValue: "drop",
},
前端给目标元素写上这个属性:
function DropZone() {
window.go.main.App.OnFileDrop(
(x, y, paths) => console.log(paths),
true // 开启拖放目标检测
);
return (
<div
style={{ "--wails-drop-target": "drop" } as React.CSSProperties}
className="drop-zone"
>
把文件拖到这里
</div>
);
}
用 CSS 给高亮状态做视觉反馈:
.drop-zone {
border: 2px dashed #ccc;
}
.drop-zone.wails-drop-target-active {
border-color: #2d8cf0;
background: #eef6ff;
}
鼠标移到框上,边框变蓝,用户体验就清晰了。
25-5 关掉网页原生拖放
默认情况下,WebView 自己也认拖放。拖个文件进去,浏览器会试图打开它,干扰你的逻辑。
不想让网页自己处理,把 DisableWebViewDrop 打开:
DragAndDrop: &options.DragAndDrop{
EnableFileDrop: true,
DisableWebViewDrop: true,
},
这样文件只会走 Wails 的 OnFileDrop,不会触发浏览器的打开行为。
Tip做”拖文件上传”功能时,强烈建议开
DisableWebViewDrop。否则文件偶尔会被浏览器抢走,你的回调收不到,调试起来很懵。
25-6 实战:拖图片进预览区
做个”拖图片进预览”的小功能。目标区域高亮,松手后把第一张图读出来显示。
function DropZone() {
window.go.main.App.OnFileDrop((x, y, paths) => {
if (paths.length > 0) {
window.go.main.App.LoadImage(paths[0]);
}
}, true);
return <div style={{ "--wails-drop-target": "drop" } as React.CSSProperties }>拖图片到这里</div>;
}
Go 侧 LoadImage 读文件、转 base64,再 EventsEmit 给前端渲染:
func (a *App) LoadImage(path string) {
data, _ := os.ReadFile(path)
b64 := base64.StdEncoding.EncodeToString(data)
rt.EventsEmit(a.ctx, "image:loaded", "data:image/png;base64,"+b64)
}
Tip拖入的是绝对路径,Go 能直接
os.ReadFile。别在前端用fetch去读本地路径,浏览器安全策略会拦,交给后端读最稳。
25-7 多文件与错误处理
多文件和错误处理要提前想好。
用户可能一次拖进十张图,也可能拖了个文件夹。paths 是切片,循环处理才有扩展性。读文件失败(权限、损坏)要兜底,别让一个坏文件崩了整个回调。
func (a *App) LoadImages(paths []string) {
var ok []string
for _, p := range paths {
if data, err := os.ReadFile(p); err == nil {
ok = append(ok, toDataURL(p, data))
}
}
rt.EventsEmit(a.ctx, "images:loaded", ok)
}
Warning拖放回调里读文件是同步阻塞的。文件很大或很多时,会卡住界面。真做批量导入,建议起 goroutine 处理,读完再 EventsEmit 回前端。
25-8 与右键菜单协同及安全
拖放和右键菜单经常出现在同一个界面里,比如一个文件列表。
左键拖文件进列表就导入,右键点某个文件弹”删除 / 重命名”。两者互不冲突,因为拖放走 OnFileDrop 的 drop 事件,右键走 mousedown 的 button 判断。
function FileList() {
window.go.main.App.OnFileDrop((x, y, paths) => {
window.go.main.App.ImportFiles(paths);
}, true);
function onCtx(e: MouseEvent) {
if (e.button === 2) {
e.preventDefault();
window.go.main.App.ShowFileMenu(e.clientX, e.clientY);
}
}
return <ul onMouseDown={onCtx}>...</ul>;
}
拖放目标区域(带 --wails-drop-target 的元素)如果同时是右键菜单的容器,两者能和平共存。因为一个是 drop 事件,一个是 mousedown 事件,触发时机不同。
拿到路径后别直接信任。拖进来的可能是文件夹、快捷方式或权限受限的文件。导入前做一层校验:检查扩展名是否在白名单、用 os.Stat 确认是普通文件、读之前再试一次 os.ReadFile 兜底。
func (a *App) ImportFiles(paths []string) {
var ok []string
for _, p := range paths {
if info, err := os.Stat(p); err != nil || info.IsDir() {
continue // 跳过目录和读不到的
}
if !strings.HasSuffix(p, ".png") && !strings.HasSuffix(p, ".jpg") {
continue // 只收图片
}
ok = append(ok, p)
}
rt.EventsEmit(a.ctx, "files:imported", ok)
}
Note拖放目标区域如果同时是右键菜单容器,两者能共存。一个是 drop、一个是 mousedown,触发时机不同,不会打架。
Tip文件列表更新后,把最新路径通过事件回传前端让 React 重新渲染。Go 侧改了数据,前端不会自动知道,事件桥是唯一的同步通道。校验不合格的默默跳过即可,别弹一堆报错吓用户。
25-9 排错清单
拖放的排错清单。
第一,收不到任何拖放。先确认 EnableFileDrop 设为 true,这是最常见的漏网。
第二,目标区域不高亮。确认 useDropTarget 为 true,且元素写了 --wails-drop-target: drop(和你 options 里的 CSSDropProperty 一致)。
第三,文件被浏览器抢走打开。关掉 DisableWebViewDrop。
第四,回调只拿到一个文件。用户可能拖了多个,paths 是数组,循环处理才稳。
Tip拖放没反应时,先用
EventsOn("wails:file-drop", ...)这个事件通道验证,它能绕过绑定直接收数据,帮你判断是开关问题还是绑定问题。另外,拖放和文件选择对话框(前面章节讲过)能互补:拖放图省事,对话框图精准。两者都提供,用户按习惯选,体验最佳。
常见误区
Warning
EnableFileDrop不设为 true,后面任何监听都收不到。先检查开关,再查代码。
Note
useDropTarget为 true 时,只有带--wails-drop-target: drop的元素会被识别。忘了写这个属性,高亮和回调都不会触发。
Tip多个文件或文件夹一起拖入时,
paths是数组。处理逻辑要兼容多元素,别写死只认paths[0]。