ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Puppeteer ElementHandle.dragOver 解析:dragover 事件的底层派发机制与新拖放流程

Puppeteer ElementHandle.dragOver 解析:dragover 事件的底层派发机制与新拖放流程 Puppeteer ElementHandle.dragOver 解析dragover 事件的底层派发机制与新拖放流程【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer本篇围绕 Puppeteer API 文档中的 ElementHandle.dragOver() 方法展开它如何把一个dragover事件精确派发到指定元素、参数Protocol.Input.DragData的默认值从何而来以及该方法为何被官方标记为废弃。读完你将掌握 dragOver 从 API 层到 CDP 协议层的完整调用链、官方推荐的新拖放drag-and-drop替代方案并能借助仓库自带的测试与测试页面自行验证整条事件流。API 签名与参数按官方 API 文档 puppeteer.elementhandle.dragover.md方法签名如下class ElementHandle { dragOver( this: ElementHandleElement, data?: Protocol.Input.DragData, ): Promisevoid; }参数类型说明thisElementHandleElement调用该方法的元素句柄dragover事件将派发到该元素的中心点dataProtocol.Input.DragData可选拖放数据包含拖拽项列表与拖拽操作掩码返回值Promisevoid事件派发完成后 resolve。文档开头有一条明确的警告Warning: This API is now obsolete. Do not use.dragoverwill automatically be performed during dragging.也就是说官方不再建议手工调用dragOver因为在完整拖放流程中dragover会被自动派发。这条警告的“底气”就藏在源码里下面逐层拆解。源码实现ElementHandle.dragOver 的三段式流程ElementHandle.ts 中的实现只有五行核心逻辑/** * deprecated Do not use. dragover will automatically be performed during dragging. */ throwIfDisposed() bindIsolatedHandle async dragOver( this: ElementHandleElement, data: Protocol.Input.DragData {items: [], dragOperationsMask: 1}, ): Promisevoid { const page this.frame.page(); await this.scrollIntoViewIfNeeded(); const target await this.clickablePoint(); await page.mouse.dragOver(target, data); }从中可以确认三个实现细节默认参数值文档标注data为可选而源码给出的默认值是{items: [], dragOperationsMask: 1}——即空拖拽项列表加一个允许常规拖放操作的掩码。省略data时派发的就是一个不携带任何拖拽内容、仅用于触发页面dragover监听器的事件。坐标计算先scrollIntoViewIfNeeded()确保元素在视口内再由clickablePoint()计算元素中心点事件坐标最终指向元素正中央。装饰器约束throwIfDisposed()保证句柄已销毁时抛错bindIsolatedHandle负责跨执行上下文isolated world的正确绑定。方法本身并不直接与浏览器协议打交道而是把“点到哪个坐标”交给本句柄算好后转调page.mouse.dragOver(target, data)真正的协议编解码在 Mouse 层完成。Mouse 层抽象与 CDP 协议实现api/Input.ts 中Mouse抽象类定义了各平台后端必须实现的dragOver接口/** * Dispatches a dragover event. * param target - point for emitting dragover event * param data - drag data containing items and operations mask */ abstract dragOver( target: Point, data: Protocol.Input.DragData, ): Promisevoid;注释明确其语义派发一个dragover事件target是事件触发点data是包含拖拽项与操作掩码的负载。在 CDPChrome DevTools Protocol后端cdp/Input.ts 给出了具体落地方式override async dragOver( target: Point, data: Protocol.Input.DragData, ): Promisevoid { await this.#client.send(Input.dispatchDragEvent, { type: dragOver, x: target.x, y: target.y, modifiers: this.#keyboard._modifiers, data, }); }关键信息有两点底层走的是 CDP 命令Input.dispatchDragEvent事件类型字符串为dragOver与dragEnter、drop共用同一个命令、仅靠type字段区分见同文件 L496-L533。请求体中的modifiers取自当前键盘状态this.#keyboard._modifiers因此拖放过程中按住的控制键等修饰状态会被一并携带到页面事件里。需要注意的平台边界在 WebDriver BiDi 后端中bidi/Input.ts 对drag、dragOver、dragEnter、drop全部直接抛出UnsupportedOperation。也就是说dragOver 相关的拖放 API 目前仅在 CDP 通道Chrome下可用使用 BiDi 通道时无法调用。为什么废弃dragover 在拖放过程中被自动执行文档警告称 “dragoverwill automatically be performed during dragging”这在源码中可以直接验证。cdp/Input.ts 的Mouse.dragAndDrop把一整套事件序列串在了一起override async dragAndDrop( start: Point, target: Point, options: {delay?: number} {}, ): Promisevoid { const {delay null} options; const data await this.drag(start, target); // ① 触发 dragstart拿到 DragData await this.dragEnter(target, data); // ② 派发 dragenter await this.dragOver(target, data); // ③ 自动派发 dragover if (delay) { await new Promise(resolve { return setTimeout(resolve, delay); }); } await this.drop(target, data); // ④ 派发 drop await this.up(); }可以看到调用一次page.mouse.dragAndDrop(start, target, {delay})后dragOver作为第 ③ 步被无条件执行delay选项毫秒控制dragover与drop之间的等待时间默认为 0。既然完整拖放已经内建dragover再手工调用ElementHandle.dragOver只会造成重复派发这正是它被标记 obsolete 的原因。其中第 ① 步的 Mouse.drag 实现也值得留意它按顺序执行move(start) → down() → move(target)并监听 CDP 的Input.dragIntercepted事件浏览器拦截真实拖拽后回传的event.data即为Protocol.Input.DragData。这段数据随后被dragEnter/dragOver/drop复用形成一条数据贯穿整条事件链的完整流程。推荐的替代方案新拖放 API 与拦截机制既然dragOver弃用实际项目中应如何使用拖放结合 ElementHandle.ts 中同族的drag、drop、dragAndDrop方法仓库实际提供了两条路径。路径一元素级 drag drop原生 HTML5 拖放事件流ElementHandle.drag 负责“开始拖拽并移动”ElementHandle.drop 负责“落到目标上”。新版drop的推荐签名接收一个元素句柄而非原始数据// 新签名推荐 async drop(element: ElementHandleElement): Promisevoid; // 旧签名已废弃 async drop(data?: Protocol.Input.DragData): Promisevoid;其内部实现L910-L929为调用dataOrElement.drag(this)完成拖拽移动复位page._isDragging标记再执行page.mouse.up()释放鼠标。典型用法const draggable await page.$(#drag); const dropzone await page.$(#drop); await draggable.drag(dropzone); // 拖拽至目标上方 await dropzone.drop(draggable); // 在目标上完成 drop在未启用拦截的分支里drag会先hover()到源元素中心、mouse.down()按下再把鼠标移动到目标中心元素目标则通过target.hover()整个过程中浏览器会自然产生dragstart → dragenter → dragover → drop序列——这再次印证了dragover无需手工触发。路径二mouse.dragAndDrop 拖拽拦截ElementHandle.dragAndDrop标注deprecated官方建议改用ElementHandle.drop要求前置开启拖拽拦截const page this.frame.page(); assert( page.isDragInterceptionEnabled(), Drag Interception is not enabled!, ); await this.scrollIntoViewIfNeeded(); const startPoint await this.clickablePoint(); const targetPoint await target.clickablePoint(); await page.mouse.dragAndDrop(startPoint, targetPoint, options);拦截开关为 Page.setDragInterception / Page.isDragInterceptionEnabled。不过要注意这两个方法同样已被标记deprecated官方注释写道 “We no longer support intercepting drag payloads. Use the new drag APIs found on ElementHandle to drag (or just use the Page.mouse)”——即拦截拖拽负载DragData的能力正在被新元素级拖放 API 取代。对已有代码mouse.dragAndDrop仍是可用的底层入口但新项目建议优先采用路径一的dragdrop。测试验证用状态码确认整条事件链仓库自带完整的拖放测试与测试页面可以直接用来验证dragOver在事件序列中的位置。测试页面 test/assets/input/drag-and-drop.html 监听四个事件并把状态位追加到#drag-state的文本中dragstart记1、dragenter记2、dragover记3、drop记4。对应测试 test/src/drag-and-drop.test.ts 的断言链条it(should emit a dragOver event, async () { // ... const data await draggable.drag({x: 1, y: 1}); assert(data instanceof Object); using dropzone (await page.$(#drop))!; await dropzone.dragEnter(data); await dropzone.dragOver(data); // 手工派发 dragOver expect(await getDragState()).toBe(123); // 1draft, 2enter, 3over }); it(can be dropped, async () { // ... await dropzone.dragEnter(data); await dropzone.dragOver(data); await dropzone.drop(data); expect(await getDragState()).toBe(12334); // 末尾 4 drop 生效 });从测试还能读出两个实现事实draggable.drag({x: 1, y: 1})的返回值是Protocol.Input.DragData对象测试断言data.items长度为 1与 ElementHandle.drag 在拦截开启时return await page.mouse.drag(source, target)的返回路径一致另一用例L93-L100先page.setDragInterception(true)再draggable.dragAndDrop(dropzone)同样得到状态码12334说明拦截路径下的自动dragOver对应Mouse.dragAndDrop第 ③ 步与手工派发等价。使用注意事项综合文档警告与源码使用dragOver及相关拖放 API 时需注意优先使用新 APIdragOver、dragEnter、dragAndDrop以及基于setDragInterception的拦截式拖放均被废弃或建议替换新的元素级drop(element)是官方推荐入口dragover事件在拖放过程中会自动派发。平台限制Mouse.dragOver仅在 CDP 后端可用BiDi 后端对dragOver抛出UnsupportedOperation跨浏览器方案需改用模拟鼠标事件等替代手段。data 参数语义省略data时默认值为{items: [], dragOperationsMask: 1}仅触发监听器而不携带拖拽内容若需要在drop时读取自定义负载应让数据经由真实dragstartdataTransfer.setData注入由drag返回的DragData自动流转。坐标系事件派发点为元素clickablePoint()计算的中心点且会先自动滚动元素进入视口若页面使用transform等布局中心点计算依赖 CDP 的框模型复杂场景建议用测试页面确认命中区域。小结ElementHandle.dragOver是 Puppeteer 早期“分步手工派发拖放事件”模式的一部分API 层计算元素中心点CDP 层 通过Input.dispatchDragEventtype: dragOver把事件连同修饰键与DragData注入页面。随着mouse.dragAndDrop等组合 API 把dragenter → dragover → drop序列自动化该方法已无单独调用的必要并被标记废弃。新代码应使用元素级dragdrop完成拖放如需理解事件流转细节可直接运行 test/src/drag-and-drop.test.ts 与 测试页面 中的状态码断言来验证。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表