- 前端
- UI组件
- 后端
【免费下载链接】uppy
The next open source file uploader for web browsers :dog:
导读
@uppy/drag-drop是 Uppy 生态中专用于提供"拖拽放置区域"(Droppable Zone)的 UI 插件:用户把文件拖进该区域即可加入上传队列,也可以点击区域打开系统文件选择对话框。本文以该插件在 当前仓库 中的官方文档与源码实现为骨架,覆盖其安装方式、最小示例、全部可配置选项,并深入DragDrop.tsx与 Uppy Core 工具函数,剖析拖拽事件处理、文件夹递归解析、浏览器能力检测与上传限制联动的底层原理,帮助你既会用、也能看懂它为什么这样工作。
插件定位:Uppy 的 Droppable Zone
@uppy/drag-drop在 Uppy 插件体系中的类型是acquirer(获取者),即在用户上传之前负责"收集"文件的插件。它在页面上渲染一个可拖拽的放置区域("Droppable zone UI for Uppy"),并同时具备两种取文件方式:
- 拖拽投放:把本地文件或文件夹拖入区域;
- 点击浏览:点击区域弹出系统文件选择框。
与@uppy/dashboard这类完整界面不同,DragDrop只渲染一个独立的放置区,非常适合嵌入已有页面布局、与自定义上传按钮搭配,或作为简易上传表单的核心交互组件。其组件实现位于 DragDrop.tsx,最终渲染为一个<button>元素,内含隐藏的<input type="file">,无需任何额外 DOM 容器。
安装
通过 npm 安装插件本体及其运行时依赖@uppy/core(peer 依赖):
npm install @uppy/drag-drop按 package.json 的exports声明,安装后可引入的入口包括:
@uppy/drag-drop:插件主入口(lib/index.js);@uppy/drag-drop/css/style.css、@uppy/drag-drop/css/style.min.css:编译后的样式;@uppy/drag-drop/css/style.scss:可直接参与 Sass 构建的源码样式。
若使用 Transloadit Smart CDN 提供的预构建 bundle,Uppy会挂载到全局window.Uppy对象上,此时无需打包器也可直接使用本插件。
基础用法:三行代码接入
官方 README 给出的最小示例(packages/@uppy/drag-drop/README.md):
import Uppy from '@uppy/core' import DragDrop from '@uppy/drag-drop' const uppy = new Uppy() uppy.use(DragDrop, { target: '#upload', })执行流程是:
new Uppy()创建核心实例;uppy.use(DragDrop, { target: '#upload' })注册插件,并将放置区挂载到页面中#upload元素内(install()方法中通过this.mount(target, this)完成);- 用户拖入或点选文件后,插件把文件以 descriptor 形式交给
uppy.addFiles(),进入 Uppy 的全局状态管理。
若页面已有拖拽 HTML5 能力之外的样式需求,还需引入样式,例如:
import '@uppy/drag-drop/css/style.min.css'样式文件中定义的.uppy-DragDrop-container等类名详见 style.scss。
可配置选项全解析
插件公开的选项类型DragDropOptions定义在 DragDrop.tsx,与默认值(defaultOptions,见 DragDrop.tsx)合并后生效。汇总如下:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
target | string \| HTMLElement | — | 插件挂载的 DOM 目标(继承自UIPluginOptions),README 示例中传'#upload' |
inputName | string | 'files[]' | 隐藏<input type="file">的name属性,便于配合传统表单提交 |
width | string \| number | '100%' | 放置区域宽度,直接作用于容器的style.width |
height | string \| number | '100%' | 放置区域高度,直接作用于容器的style.height |
note | string | 空 | 显示在区域内的辅助提示文字(如"单个文件不超过 5MB") |
onDragOver | (event: DragEvent) => void | — | 文件拖入区域时回调 |
onDragLeave | (event: DragEvent) => void | — | 拖拽离开区域时回调 |
onDrop | (event: DragEvent) => void | — | 文件投放完成后回调 |
locale | LocaleStrings | 见下节 | 区域文案的本地化字符串 |
从源码(DragDrop.tsx)可以看到,三个on*回调分别在handleDragOver、handleDragLeave、handleDrop内部被调用,可用于实现拖拽高亮、统计投放次数等自定义交互逻辑。
与 Uppy 限制规则的联动
值得注意的一点是:@uppy/drag-drop不会重复实现文件校验,而是直接读取 Uppy 核心实例上的restrictions配置,并把它反映到隐藏的 file input 上(DragDrop.tsx):
multiple属性由restrictions.maxNumberOfFiles !== 1决定——若限制只能传一个文件,则禁止多选;accept属性由restrictions.allowedFileTypes?.join(', ')生成——在系统文件对话框中预先过滤文件类型。
因此,你只需要在new Uppy({ restrictions: { ... } })中统一配置限制,拖拽区便会自动遵循,无需重复传参。
源码原理:四步事件处理链
整个插件的拖拽逻辑集中在 DragDrop.tsx 的三个事件处理器中:
1.handleDragOver—— 准入检查与视觉反馈
- 读取
event.dataTransfer.types,检查其中是否包含'Files'类型;若拖入的是链接、文本等非文件内容,则拒绝; - 检查
this.uppy.getState().allowNewUpload,当 Uppy 正在上传且不允许新任务时同样拒绝; - 拒绝时设置
dropEffect = 'none'并中止;允许时设置dropEffect = 'copy',并把isDraggingOver状态置为true,触发.uppy-DragDrop--isDraggingOver样式(背景变灰、边框变蓝)。
2.handleDragLeave—— 状态复位将isDraggingOver重置为false,恢复默认外观。
3.handleDrop—— 取文件并入库调用 Uppy Core 工具函数getDroppedFiles(event.dataTransfer, { logDropError })异步取回所有文件,非空时记录日志并调用this.addFiles(files)。
4.addFiles—— 构造 descriptor把每个File映射为{ source, name, type, data, meta }结构,其中meta.relativePath取自file.relativePath(用于保留文件夹层级),再统一交给uppy.addFiles()进入 Uppy 状态树;若被Restricter拒绝,错误会通过uppy.log记录而不会中断。
点击路径:render()把整个区域渲染为<button>,onClick触发隐藏 input 的click();onInputChange处理change事件后会把input.value清空(DragDrop.tsx),这样即使用户重复选择同一个文件也能再次触发change。
文件夹拖拽:getDroppedFiles的双策略
放置区支持直接拖入文件夹。其能力来自 Uppy Core 的 getDroppedFiles/index.ts:它优先尝试webkitGetAsEntryApi(利用webkitGetAsEntry()递归遍历目录,把文件夹拍平为文件数组,并为每个文件补上relativePath),若浏览器不支持或解析出错,则回退到fallbackApi仅返回第一层文件。因此:
- 在 Chrome、Mozilla、Safari 等支持
webkitGetAsEntry的浏览器中拖入文件夹,可保留相对路径结构(如docs/Old Prague/airbnb.pdf); - 在不支持的浏览器中,仅能取到拖拽项的顶层文件。
getDroppedFiles返回的是 Promise,即便个别目录解析失败(如 Windows 上文件夹名过长),也会通过logDropError记录后继续解析其余文件,保证整体投放流程不中断。
浏览器能力检测
插件在构造时即执行isDragDropSupported()并缓存结果(DragDrop.tsx),用于给容器追加uppy-DragDrop--isDragDropSupported类(决定是否显示虚线边框)。检测逻辑见 isDragDropSupported.ts:
- 在服务端渲染(无
window/document.body)时直接返回false; - 依次检查
draggable、ondragstart、ondrop属性以及FormData、FileReader是否存在; - 移动端浏览器通常不满足这些条件,因而不会显示虚线"可拖放"样式,但点击浏览文件的能力依然可用。
国际化文案
默认文案定义在 locale.ts,仅两条字符串:
| key | 默认文案 | 用途 |
|---|---|---|
dropHereOr | Drop here or %{browse} | 区域主文案,%{browse}会被替换为可点击的"browse"链接 |
browse | browse | 打开系统文件对话框的链接文本 |
通过locale选项可整体替换(%{browse}占位符会被源码中以.uppy-DragDrop-browse高亮的<span>渲染),实现全中文界面只需:
uppy.use(DragDrop, { target: '#upload', locale: { strings: { dropHereOr: '将文件拖到此处,或%{browse}', browse: '浏览', }, }, })样式定制
放置区的基础视觉由 style.scss 提供,关键类名及作用:
.uppy-DragDrop-container:flex 居中布局、圆角、白底与cursor: pointer,聚焦时显示蓝色外发光;.uppy-DragDrop-inner:内边距与居中文案容器;.uppy-DragDrop-arrow:内置的上传箭头 SVG 图标(renderArrowSvg()渲染,aria-hidden);.uppy-DragDrop--isDragDropSupported:灰色虚线边框,标识"可拖放";.uppy-DragDrop--isDraggingOver:拖拽悬停态,背景变浅灰、边框变蓝;.uppy-DragDrop-note:note选项提示文字的颜色与字号。
需要深度定制时,可引入@uppy/drag-drop/css/style.scss参与自己的 Sass 构建,覆盖上述类名即可。
小结
@uppy/drag-drop是一个"小而专"的 Uppy 插件:它只负责提供拖拽/点选入口,把文件统一交还给 Uppy 核心,从而自动获得核心的全局状态管理、restrictions校验与后续上传链路。理解它的选项、事件回调与getDroppedFiles/isDragDropSupported两条底层工具链,可以让你在自定义上传界面时精准掌控拖拽体验,并为移动端、限制规则、文件夹结构保留等边界场景做针对性处理。更多上下文可继续阅读插件入口 index.ts、核心实现 DragDrop.tsx 与官方文档页。
- 前端
- UI组件
- 后端
【免费下载链接】uppy
The next open source file uploader for web browsers :dog:
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考