尧图精选

InvokeAI 前端拖拽体系深度解析:基于 Pragmatic Drag and Drop 的 dnd 模块设计与实现

🕒 发布时间:2026/9/10 22:48:46 📁 来源:尧图网络
InvokeAI 前端拖拽体系深度解析基于 Pragmatic Drag and Drop 的 dnd 模块设计与实现【免费下载链接】InvokeAIInvoke is a leading creative engine for Stable Diffusion models, empowering professionals, artists, and enthusiasts to generate and create visual media using the latest AI-driven technologies. The solution offers an industry leading WebUI, and serves as the foundation for multiple commercial products.项目地址: https://gitcode.com/GitHub_Trending/in/InvokeAIInvokeAI 的 WebUI 在画廊Gallery、画布Canvas、节点工作流Node Workflow等多个核心场景中重度依赖拖拽交互。本文以invokeai/frontend/web/src/features/dnd/README.md为骨架结合dnd.ts、useDndMonitor.ts、DndDropTarget.tsx、FullscreenDropzone.tsx等源码文件系统讲解 InvokeAI 如何基于 Atlassian Pragmatic Drag and Drop 库构建一套类型安全、可复用的拖拽体系。读完本文你将理解其 Source / Target / Monitor 三要素抽象、Symbol 类型守卫机制、isValid/handler 回调契约以及它在画布图层排序、节点表单构建、全屏文件上传等场景中的实战应用并掌握如何仿照dnd.ts中的既有模式扩展新的拖拽源与拖拽目标。一、技术选型为什么是 Pragmatic Drag and DropInvokeAI 前端对拖拽功能的选择经历了明确的演进react-beautiful-dnd的作者Alex Reardon 所在的 Atlassian 团队在维护了多年经典拖拽库之后将其理念与经验沉淀为新一代的Pragmatic Drag and Dropatlaskit/pragmatic-drag-and-drop依赖当前版本为^1.7.7。README 中明确说明react-beautiful-dnd已不再活跃维护InvokeAI 采用了它的继任者。Pragmatic Drag and Drop 的设计哲学与传统 React 拖拽库截然不同基于原生 HTML5 拖拽 API它直接封装dragstart/dragover/drop等原生事件而不是自己模拟一套指针追踪。这意味着性能开销极低——拖拽过程中不触发 React 渲染只在关键节点进入、离开、放下通知应用。不暴露 React 组件 API传统库通常提供DragDropContext、Draggable、Droppable这类 JSX 组件Pragmatic Drag and Drop 只提供一组工具函数draggable、dropTargetForElements、monitorForElements等通过命令式方式挂接到任意 DOM 元素上。初始学习曲线陡峭README 直言 The library is a bit to wrap your head around但一旦理解了核心概念its very nice to work with and super flexible——这是 InvokeAI 选择它的核心理由足够的灵活性支撑画廊、画布、节点、工作流表单等完全不同形态的拖拽需求。package.json中还可以看到三个配套包被一同引入它们各自承担了不同的能力依赖包版本作用atlaskit/pragmatic-drag-and-drop^1.7.7核心库draggable / droppable / monitor 三要素atlaskit/pragmatic-drag-and-drop-auto-scroll^2.1.2拖拽过程中容器自动滚动如长画廊列表atlaskit/pragmatic-drag-and-drop-hitbox^1.1.0最近边缘检测、列表重排closest-edge、reorder-with-edge二、核心架构Source、Target 与 Monitor 三要素README 定义了这套体系的三个基本概念它们对应 Pragmatic Drag and Drop 库中的draggable elements、droppable elements和dnd monitorsDnd Source拖拽源——任何提供可拖拽载荷payload的东西。在 InvokeAI 中目前有两种形态的载荷单个图片 DTOImageDTO一组图片名称image_names连同它们所属的画板origin board。Dnd Target拖拽目标——任何可以接受该载荷放下的东西。目标携带自己的数据例如一个画板board ID、一个画布图层layer ID、一个节点字段fieldIdentifier等。Dnd Monitor拖拽监视器——不可见的监听元素追踪整个拖拽过程并向应用报告当前拖拽操作的信息。它是旁观者角色不参与拖拽本身但可以决定是否监视canMonitor并在放下时响应onDrop。三者之间通过**元素数据element data**进行通信拖拽开始时 source 通过getInitialData()注入数据target 通过getData()暴露自身数据monitor 在onDrop时从location.current.dropTargets[0]拿到最顶层 target 的数据。InvokeAI 将这一切封装在features/dnd目录中组件层只需要使用这些封装好的抽象。三要素的落地形态features/dnd目录下的文件分工如下文件职责dnd.ts全部 Source / Target 的类型定义、typeGuard、getData 工厂与注册表types.tsTarget 三态状态机idle / potential / over与列表拖拽状态useDndMonitor.ts全局唯一的 drop 监视器集中分发 drop 到匹配的 TargetDndDropTarget.tsx通用可复用 drop 目标组件驱动状态机并渲染覆盖层DndImage.tsx可拖拽图片组件封装 source 注册与自定义拖拽预览DndDropOverlay.tsx放下时的覆盖层视觉反馈DndDragPreviewSingleImage.tsx单图自定义拖拽预览DndDragPreviewMultipleImage.tsx多图自定义拖拽预览DndListDropIndicator.tsx列表重排时的插入位置指示线FullscreenDropzone.tsx全屏外部文件投放区含粘贴上传fullscreenDropzoneAccept.ts外部文件类型校验zoduseDndMonitor.ts见上util.ts拖拽预览偏移、落点闪光动画、input 聚焦修复等工具函数三、类型安全机制Symbol 注入与 TypeGuard原生拖拽事件本身没有任何内置类型安全——event.dataTransfer传输的是任意字符串或对象应用外部如浏览器其他标签页甚至可以往你的 drop 目标里塞任意数据。InvokeAI 的解决方案非常精巧每个 Source 和 Target 在定义时都通过buildTypeAndKey生成一个唯一的Symbol并注入到数据对象中通过 typeguard 函数校验该 Symbol 是否存在从而确认这个载荷确实是我们定义的类型而不是从应用外部或其他来源误放的数据。以 dnd.ts 中的核心工厂为例type DndDataType extends string, PrivateKey extends symbol, Payload { [key in PrivateKey]: true; } { id: string; type: Type; payload: Payload; }; const buildTypeAndKey T extends string(type: T) { const key Symbol(type); return { type, key } as const; }; const buildTypeGuard T extends DndData(key: symbol) { const typeGuard (val: RecordUnknown): val is T Boolean(val[key]); return typeGuard; }; const buildGetData T extends DndData(key: symbol, type: T[type]) { const getData (payload: T[payload], id?: string): T ({ [key]: true, id: id ?? getPrefixedId(type), type, payload }) as T; return getData; };这段代码揭示了几个关键设计RecordUnknown Recordstring | symbol, unknown由于 Symbol 键在 TypeScript 类型中属于隐私键拖拽数据必须以Recordstring | symbol, unknown形式接收才能通过 typeGuard 收窄。每个数据对象都含id、type、payload三个公共字段id默认由getPrefixedId(type)生成保证每次拖拽会话拥有唯一标识payload是业务数据本体。typeGuard 是一个类型谓词val is T一旦val[key]为真TypeScript 就能把整个对象收窄为对应类型——这是整个体系类型安全的基石。一个具体的 Source 定义单图拖拽源如下const _singleImage buildTypeAndKey(single-image); export type SingleImageDndSourceData DndData typeof _singleImage.type, typeof _singleImage.key, { imageDTO: ImageDTO } ; export const singleImageDndSource: DndSourceSingleImageDndSourceData { ..._singleImage, typeGuard: buildTypeGuard(_singleImage.key), getData: buildGetData(_singleImage.key, _singleImage.type), };type: single-image字符串用于调试日志key: Symbol(single-image)用于运行时校验两者由buildTypeAndKey保证一一对应。即使外部构造出形如{ type: single-image, payload: {...} }的对象只要没有那个 SymboltypeGuard 就会返回 false。四、Source 与 Target 的类型化注册表README 强调These are strictly typed in the dnd.ts file. Follow the examples there to define new sources and targets.——所有 Source/Target 集中定义在 dnd.ts 中新增拖拽能力时参照既有示例即可。4.1 Source 清单目前 dnd.ts 定义了 6 个 Source覆盖图片与视频的单项/多项拖拽以及画布实体拖拽Source 常量类型字符串payload典型使用场景singleImageDndSourcesingle-image{ imageDTO: ImageDTO }画廊缩略图拖到画布、节点字段、画板singleVideoDndSourcesingle-video{ videoDTO: VideoDTO }视频缩略图拖到节点视频字段multipleImageDndSourcemultiple-image{ image_names: string[]; video_names: string[]; board_id: BoardId }画廊多选后批量拖拽multipleVideoDndSourcemultiple-video{ video_names: string[]; image_names: string[]; board_id: BoardId }画廊多选含视频批量拖拽singleRefImageDndSourcesingle-ref-image{ id: string }参考图列表内部重排reordersingleCanvasEntityDndSourcesingle-canvas-entity{ entityIdentifier: CanvasEntityIdentifier }画布图层列表排序值得注意的细节multipleImageDndSource的注释dnd.ts说明当从图片缩略图拖出混合选择时image_names是主载荷用于拖拽预览标题和图片集合字段投放video_names则顺路携带——这样画板 drop 处理器能在两个数组都非空时同时派发两种变更。multipleVideoDndSource与之对称。4.2 Target 的类型契约Target 比 Source 复杂它带有两个回调。DndTarget类型dnd.ts定义如下type DndTargetTargetData extends DndData, SourceData extends DndData { key: symbol; type: TargetData[type]; typeGuard: ReturnTypetypeof buildTypeGuardTargetData; getData: ReturnTypetypeof buildGetDataTargetData; isValid: (arg: { sourceData: RecordUnknown; targetData: TargetData; dispatch: AppDispatch; getState: AppGetState; }) boolean; handler: (arg: { sourceData: SourceData; targetData: TargetData; dispatch: AppDispatch; getState: AppGetState; }) void; };两个回调的契约如下isValid在拖拽过程中被反复调用参数为当前被拖拽的 source 数据用于判断该 target 是否能够接受这次 drop。README 指出典型实现就是直接用 source 的 typeGuard 函数做一次类型检查但也完全可以在其中做更复杂的业务判断见下文拖到画板的例子它会读取 RTK Query 缓存来检查来源画板的所有权。handlerdrop 发生时被调用负责执行落地动作。README 指出典型实现是派发一个或多个 Redux action 来更新状态。两者都能拿到sourceData、targetData、dispatch与getState来自getStore()这意味着 isValid 和 handler 拥有访问整个 Redux store 的能力可以做任意复杂的校验与状态变更。当前 dndTargets 注册表 共包含 13 个 TargetTarget 常量类型字符串接受的 Sourcehandler 行为setGlobalReferenceImageDndTargetset-global-reference-imagesingle-image将图片设为全局参考图addGlobalReferenceImageDndTargetadd-global-reference-imagesingle-image新增一条全局参考图配置setRegionalGuidanceReferenceImageDndTargetset-regional-guidance-reference-imagesingle-image设置区域引导参考图setUpscaleInitialImageDndTargetset-upscale-initial-imagesingle-image作为放大upscale的初始图setNodeImageFieldImageDndTargetset-node-image-field-imagesingle-image填充节点图片字段setNodeVideoFieldVideoDndTargetset-node-video-field-videosingle-video填充节点视频字段addImagesToNodeImageFieldCollectionDndTargetadd-images-to-image-collection-node-fieldsingle / multiple-image向图片集合字段追加图片setComparisonImageDndTargetset-comparison-imagesingle-image设为对比图不允许重复选择同一张newCanvasEntityFromImageDndTargetnew-canvas-entity-from-imagesingle-image从图片创建新画布实体newCanvasFromImageDndTargetnew-canvas-from-imagesingle-image从图片创建新画布replaceCanvasEntityObjectsWithImageDndTargetreplace-canvas-entity-objects-with-imagesingle-image用图片替换画布实体内容addImageToBoardDndTargetadd-to-boardimage / video 单多项将图片/视频移入画板removeImageFromBoardDndTargetremove-from-boardimage / video 单多项将图片/视频移出画板回到未分类4.3 三种有代表性的 Target 实现1最简形态——typeGuard 单个 action 派发。以setUpscaleInitialImageDndTarget为例isValid 只做类型守卫handler 直接调用setUpscaleInitialImage({ imageDTO, dispatch })完成状态更新这正是 README 描述的标准模板。2需要 Redux 状态参与校验——setComparisonImageDndTarget。它的 isValid 在类型守卫之外还通过selectComparisonImages(getState())读取当前对比图拒绝把与第一张或第二张相同的图片再次设为对比图dnd.ts。这展示了 isValid 携带getState的价值校验逻辑可以完全依赖当前应用状态。3集合字段的复杂处理——addImagesToNodeImageFieldCollectionDndTarget。它的 handler 需要先通过selectFieldInputInstanceSafe读取目标节点字段的当前输入实例用isImageFieldCollectionInputInstance确认这是一个图片集合字段否则打log.warn并安全返回将单图 payload 的image_name或多图 payload 的image_names展开追加到现有值最后派发fieldImageCollectionValueChanged写回。这个 Target 的泛型参数是SingleImageDndSourceData | MultipleImageDndSourceData联合类型isValid 中对两种 source 都做了 typeGuardhandler 中则用 if 分支收窄后再处理——这是多 Source 联合 Target 的标准写法。4.4 画板移动权限校验isValid 的业务深度addImageToBoardDndTarget和removeImageFromBoardDndTarget展示了 isValid 能做到多深的业务校验。它们依赖canMoveFromSourceBoarddnd.ts辅助函数单用户模式无认证state.auth?.user为空——总是允许管理员——总是允许来源画板为none未分类——允许否则在 RTK Query 缓存state.api?.queries中查找该画板画板所有者允许移动非所有者只有在画板可见性为public时才能移动缓存中找不到画板时默认放行避免阻塞合法操作。视频的注释还揭示了一个前后端协作的细节后端额外强制执行_assert_video_direct_owner客户端无法在缺少每个视频 owner 信息的情况下执行该检查所以视频场景允许校验失败冒泡到后端 mutation 报错而不是在前端预先阻断。这是 isValid 能力边界的一个务实取舍。五、全局 Drop 分发useDndMonitor 的集中式设计Source 与 Target 定义了谁能拖、谁能接但真正把 drop 事件路由到正确 Target 的是全局监视器 useDndMonitor.ts。它是一个单例 hookuseAssertSingleton(useDropMonitor)保证整个应用只挂载一次逻辑如下canMonitor白名单过滤只监视四种媒体 Sourcesingle/multiple image、single/multiple video。注释明确指出如果这里漏掉multipleVideoDndSource多视频拖拽在全局监视器层面就会被忽略drop 会静默失败。onDrop分发从location.current.dropTargets[0]取最顶层 target 数据遍历dndTargets注册表用每个 target 的typeGuard匹配目标类型匹配成功后先调用isValid通过后调用handler然后return结束分发第一个匹配即胜出若没有任何 target 匹配或校验失败则记录log.warn(Invalid drop)。combine清理atlaskit/pragmatic-drag-and-drop/combine是库提供的组合工具把多个监听器/配置合并为一个返回值组件卸载时统一清理。由于分发逻辑完全集中在dndTargets注册表与 useDndMonitor 中新增一个 Target 的接入成本被降到最低在 dnd.ts 里定义 → 加入dndTargets数组 → 组件侧用DndDropTarget挂载即可无需改动监视器。六、组件层封装DndDropTarget、DndImage 与视觉反馈6.1 DndDropTarget通用投放目标组件DndDropTarget.tsx 把注册 drop target 维护状态机 渲染覆盖层打包成一个泛型组件type PropsT extends AnyDndTarget { dndTarget: T; dndTargetData: ReturnTypeT[getData]; label: string; isDisabled?: boolean; };组件内部用useEffect同时注册两个监听dropTargetForElements绑定到自身 DOM 元素canDrop直接调用dndTarget.isValidgetData返回dndTargetData进入/离开时更新dndStatemonitorForElements以旁观者身份监视同一目标onDragStart时把状态置为potential拖拽开始但尚未悬停到目标上onDrop时重置为idle。状态机定义在 types.tsidle没有拖拽发生或当前拖拽对该目标无效potential拖拽正在进行且对该目标有效但指针尚未悬停在目标上方over拖拽正在进行、对该目标有效、且指针正悬停在目标上方。渲染时DndDropOverlay根据状态渲染覆盖层idle 时不渲染任何内容potential/over时显示半透明遮罩 虚线边框 标签文字悬停over时边框和文字会高亮为品牌黄色invokeYellow.300。此外样式里有一个关键细节idle 状态下pointerEvents: noneDndDropTarget.tsx避免透明覆盖层挡住下方元素的点击事件。6.2 DndImage可拖拽图片组件DndImage.tsx 是画廊缩略图的可拖拽化封装展示了 Source 侧的完整生命周期通过draggable({ element, getInitialData: () singleImageDndSource.getData({ imageDTO }, imageDTO.image_name), ... })注册拖拽注意传入的id直接复用imageDTO.image_name保证同一张图的每次拖拽共享稳定的数据标识onDragStart/onDrop维护isDragging状态拖动中图片透明度降为 0.3data-is-draggingtrueonGenerateDragPreview中通过setSingleImageDragPreview生成自定义拖拽预览而非系统默认的半透明快照同时叠加useMiddleClickOpenInNewTab中键新标签打开原图与useImageContextMenu右键菜单等能力。6.3 自定义拖拽预览与工具函数DndDragPreviewSingleImage.tsx 使用setCustomNativeDragPreview把 React 组件渲染进原生拖拽预览层createPortal到容器预览图尺寸固定为 32 主题单位DND_IMAGE_DRAG_PREVIEW_SIZE。util.ts 则沉淀了三个跨场景复用的工具preserveOffsetOnSourceFallbackCentered预览偏移函数。模仿库内置的preserveOffsetOnSource保持鼠标相对源元素的位置但当偏移超出容器边界时回退为居中——避免从容器边缘拖出时预览飞出视口。triggerPostMoveFlash落点闪光动画。基于 Atlassian 官方flourish包中的trigger-post-move-flash移植而来官方包依赖过重直接复制了函数通过 Web Animations API 在放下后的元素上播放 700ms 的背景色淡出给用户到位了的反馈。dndInputFix针对已知浏览器 bug 的修复。当可拖拽元素内部含有input/textarea时浏览器存在无法选中其文本的问题见 pragmatic-drag-and-drop issue #111 与 Firefox bug 1853069。该函数在鼠标悬停到输入控件上时临时把draggable置为false、移出时恢复trueREADME 和源码都建议在每一个 draggable 上使用它。七、Dnd 在其他场景中的应用README 专门列出三处同一库的其他用法它们与features/dnd的注册表体系并行展示了 Pragmatic Drag and Drop 的通用性。7.1 标签页悬停切换useCallbackOnDragEnteruseCallbackOnDragEnter.ts 实现拖拽经过某个标签页并停留片刻自动切换过去的交互类似浏览器标签栏拖拽换页。核心是一个 300ms 的定时器 hook用dropTargetForElements和dropTargetForExternal同时监听内部元素拖拽与外部文件拖拽onDragEnter启动延迟回调默认delay 300onDragLeave取消。由于dropTargetForExternal的存在从操作系统文件管理器拖文件经过标签页同样会触发切换——这是画廊标签栏Gallery / Batch / Nodes 等的常见体验。7.2 画布图层列表重排README 指向 CanvasEntityGroupList.tsx 与 useCanvasEntityListDnd.ts。后者演示了基于 hitbox 的列表重排完整链路每个图层项同时注册draggable用singleCanvasEntityDndSource.getData({ entityIdentifier })提供数据与dropTargetForElementsdrop target 的getData通过attachClosestEdge(data, { element, input, allowedEdges: [top, bottom] })计算指针距离该元素上下边缘的最近边onDragEnter/onDrag用extractClosestEdge取出最近边驱动DndListTargetState状态机is-dragging/is-dragging-overclosestEdgegetIsSticky返回true让 drop target 在拖拽中保持粘性指针略移出元素也不丢失组列表 CanvasEntityGroupList.tsx 中的monitorForElements在 onDrop 时计算 source/target 索引结合extractClosestEdge用reorderWithEdge计算新位置派发entitiesReordered并调用triggerPostMoveFlash播放落点动画渲染层 DndListDropIndicator.tsx 根据closestEdge绘制一条 2px 的插入指示线pointerEvents: none保证指示线不干扰拖拽命中。DndListTargetState还区分了preview状态携带容器引用为多容器跨列表拖拽预留了能力。7.3 节点字段表单构建器README 指出这是最复杂的一处应用dnd-hooks.ts约 600 行。表单构建器支持任意深度的容器嵌套与元素堆叠因此它没有复用features/dnd的注册表而是在本地重新实现了同样的 Symbol 哨兵模式const uniqueFormElementDndKey Symbol(form-element)源码注释明确写道 Dnd payloads are arbitrarily shaped. We can use a unique symbol as a sentinel value for our strongly-typed dnd data.用buildFormElementDndData/isFormElementDndData完成类型安全的载荷构造与守卫引入 hitbox 的attachClosestCenterOrEdge中心或最近边机制支持将字段放入容器内部center或插到容器边界edge依赖form-manipulation.ts中的getAllowedDropRegions等函数计算合法投放区域通过flushSync同步更新 ReduxformElementAdded/formElementReparented等 action实现元素新增与重挂载。这印证了 README 的评价该库一旦掌握概念就非常好用且超级灵活——连最复杂的嵌套表单都能用同一套原语拼装出来。八、全屏文件投放External Adapter 与粘贴上传features/dnd不仅处理应用内元素拖拽还通过 FullscreenDropzone.tsx 处理从操作系统拖入的文件。它使用库的 external adapterdropTargetForExternal/monitorForExternalcanDrop: containsFiles只接受文件拖入onDrop用getFiles({ source })取出 File 列表走validateAndUploadFilesmonitorForExternal在拖拽开始时调用preventUnhandled.start()——这是库提供的防止浏览器默认跳转机制避免把文件拖到页面上时浏览器直接打开该文件。validateAndUploadFiles的处理逻辑值得一提zod 校验z.array(zUploadFile).safeParse(files)。校验规则定义在 fullscreenDropzoneAccept.tsz.customFile().refine(isAcceptedUploadFile)——MIME 类型或文件扩展名任一满足即可。这是因为浏览器有时会提供空或泛化的File.type比如从某些文件管理器拖出clip.mp4若强制两个信号都满足会把后端本来就接受的文件拒之门外。对应测试 fullscreenDropzoneAccept.test.ts 明确验证了扩展名为.mp4但 MIME 为空的文件应被接受而.webm/.mov/.mkv等后端不接受的容器格式必须被拒绝。画布粘贴协同当焦点在画布标签页、活动标签也是画布、且粘贴的是单个非视频图片时交给setFileToPaste走画布粘贴流程画布可以新建图层承接否则走通用上传。按媒体类型分流图片与视频分别通过uploadImages/uploadVideos上传各自维护独立的 RTK Query 缓存并失效画廊board_id取自selectAutoAddBoardId自动添加画板设置none时上传到未分类。粘贴上传window.addEventListener(paste)监听剪贴板文件走同一条validateAndUploadFiles通路。DropLabel 会实时显示当前自动添加画板的名称toast.itemsWillBeAddedTo让用户明确知道文件将被投放到哪里。九、模式总结如何扩展新的拖拽能力综合 dnd.ts 与相关组件向 InvokeAI 前端新增一种拖拽交互的推荐路径如下均为仓库既有模式的复用无需改动监视器与分发逻辑新增 Source在dnd.ts中用buildTypeAndKey(你的-type)生成类型与 Symbol声明type XxxDndSourceData DndDatatypeof _xxx.type, typeof _xxx.key, 你的payload导出xxxDndSource: DndSourceXxxDndSourceData组合typeGuard与getData工厂在组件中通过draggable({ getInitialData: () xxxDndSource.getData(payload, id?) })挂载并搭配dndInputFix(element)。新增 Target在dnd.ts中用buildTypeAndKey定义 target 类型与数据实现DndTargetTargetData, SourceData编写isValid至少做 source typeGuard 校验与handler派发 Redux action 完成业务将新 target 加入dndTargets数组useDndMonitor会自动分发在 UI 上使用DndDropTarget组件传入dndTarget与getData生成的 target 数据或直接注册dropTargetForElements手动管理状态机。列表重排场景可参照useCanvasEntityListDnd的组合draggabledropTargetForElementsattachClosestEdge/extractClosestEdgeDndListDropIndicator最后用reorderWithEdge计算目标索引并派发排序 action。十、小结InvokeAI 的拖拽体系是一个小内核、广覆盖的典型案例核心只有一份类型化注册表dnd.ts和一个全局分发器useDndMonitor.ts却支撑起了画廊、画布、节点、工作流表单、全屏上传等十余种完全不同的拖拽交互。其技术要点可归纳为性能基于原生 HTML5 DnD API拖拽过程不经过 React 渲染配合只在状态变化时更新的精细状态管理如useCanvasEntityListDnd中相同 edge 不触发重渲染保证了长列表与大规模图库的流畅体验类型安全Symbol 哨兵 typeGuard 谓词让载荷来自我们自己的 source成为可静态验证的强约束天然免疫外部数据污染关注点分离Source 只负责提供什么Target 通过isValid/handler决定能否接受与放下后做什么Monitor 负责集中分发三者解耦让扩展成本极低视觉反馈DndDropOverlay、DndListDropIndicator、triggerPostMoveFlash等从状态到视觉的完整反馈链保证了拖拽交互的可发现性与完成感。对于想要为 InvokeAI 前端贡献拖拽相关功能例如新的画板操作、新的节点字段类型、新的列表重排场景的开发者本文第 9 节的扩展路径即是官方推荐实践的直接映射——以dnd.ts的既有示例为模板即可在保持全局类型安全与分发一致性的前提下快速接入新的拖拽能力。【免费下载链接】InvokeAIInvoke is a leading creative engine for Stable Diffusion models, empowering professionals, artists, and enthusiasts to generate and create visual media using the latest AI-driven technologies. The solution offers an industry leading WebUI, and serves as the foundation for multiple commercial products.项目地址: https://gitcode.com/GitHub_Trending/in/InvokeAI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →