ARTICLE DETAIL

资讯详情

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

Plate React 编辑器滚动性能规则:用 Passive Event Listener 消除 touch 与 wheel 滚动延迟

Plate React 编辑器滚动性能规则:用 Passive Event Listener 消除 touch 与 wheel 滚动延迟 Plate React 编辑器滚动性能规则用 Passive Event Listener 消除 touch 与 wheel 滚动延迟【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本篇技术文章围绕 Plate 仓库中 Vercel React 性能最佳实践技能集里的一条客户端规则 client-passive-event-listeners.md 展开为什么浏览器默认的 touch/wheel 监听会拖慢滚动{ passive: true }如何在 React 客户端代码中消除这一延迟以及什么场景下必须反过来显式声明passive: false。读完本文你可以掌握该规则的判定标准与正反代码示例并能对照 Plate 仓库中真实的选型、监听工具与特性检测实现把这条规则落到自己的滚动敏感型交互代码中。规则定位Vercel 性能技能集中的 client 类规则这条规则存放在 Plate 仓库的 Agent 技能目录 vercel-react-best-practices 下其 frontmatter 元数据为title: Use Passive Event Listeners for Scrolling Performanceimpact: MEDIUM消除由事件监听导致的滚动延迟tags: client, event-listeners, scrolling, performance, touch, wheel技能集 SKILL.md 将规则按影响优先级分为 8 个类别其中client-前缀代表「Client-Side Data Fetching」类优先级 4MEDIUM-HIGH。该规则与同目录的 client-event-listeners.md全局监听去重属于同一关注域客户端事件监听器的注册方式直接影响运行时性能。为什么非 passive 监听会造成滚动延迟浏览器处理touchstart、touchmove、wheel这类滚动相关事件时默认策略是执行完所有监听器后还要检查其中是否有人调用了preventDefault()以决定是否继续滚动。对于滚动这类对延迟极其敏感的操作这意味着即使你的监听器只是打一条日志、上报一个埋点浏览器也要等它跑完才能推进这一帧滚动从而产生可感知的卡顿scroll delay。为监听器加上{ passive: true }就是向浏览器明确承诺「我不会调用preventDefault()」浏览器因此可以立即执行默认滚动行为不必等监听器返回。此外还有一层背景知识现代 Chromium 内核浏览器已默认将注册在window/document/body上的touchstart、touchmove监听器按 passive 处理因此显式声明 passive 既是消除延迟的手段也是与平台默认行为对齐、避免歧义的习惯。错误与正确写法对照原始规则文档给出的反例是在useEffect中为document注册touchstart与wheel监听仅用于读取坐标做日志/埋点却没有声明 passiveuseEffect(() { const handleTouch (e: TouchEvent) console.log(e.touches[0].clientX) const handleWheel (e: WheelEvent) console.log(e.deltaY) document.addEventListener(touchstart, handleTouch) document.addEventListener(wheel, handleWheel) return () { document.removeEventListener(touchstart, handleTouch) document.removeEventListener(wheel, handleWheel) } }, [])正确写法只需在两个addEventListener调用上追加第三个参数{ passive: true }清理逻辑保持不变removeEventListener按事件类型 回调函数引用匹配这里无需额外处理useEffect(() { const handleTouch (e: TouchEvent) console.log(e.touches[0].clientX) const handleWheel (e: WheelEvent) console.log(e.deltaY) document.addEventListener(touchstart, handleTouch, { passive: true }) document.addEventListener(wheel, handleWheel, { passive: true }) return () { document.removeEventListener(touchstart, handleTouch) document.removeEventListener(wheel, handleWheel) } }, [])两个示例的共同点也值得注意清理函数必须持有与注册时同一个函数引用闭包中的handleTouch/handleWheel否则removeEventListener无法匹配到目标监听器会形成泄漏。决策标准何时用 passive何时不能用规则文档给出了明确的判定边界核心判据只有一条——监听器是否可能调用preventDefault()应该使用passive: true的场景埋点 / 分析tracking/analytics日志logging任何不需要调用preventDefault()的监听器不应使用passive: true的场景自定义滑动手势custom swipe gestures自定义缩放控制custom zoom controls任何需要调用preventDefault()的监听器原因很直接passive 监听器中调用preventDefault()会被浏览器静默忽略部分浏览器还会在控制台告警手势会被穿透到默认滚动行为上功能直接失效。所以在写这类监听器时正确姿势是显式传{ passive: false }把意图声明清楚而不是依赖默认值。Plate 仓库中的真实落地selection 包的选择区实现Plate 的 packages/selection 包实现编辑器内的框选SelectionArea其中 touch/wheel 监听的 passive 策略与上述决策标准完全吻合是这条规则在真实代码库中的完整示范。wheel 用 passivemousemove/touchmove 用非 passive在 SelectionArea.ts 中进入拖拽状态后注册了两组监听器策略截然不同第 212-214 行on(document, [mousemove, touchmove], this._onTapMove, { passive: false })——拖拽移动监听显式声明passive: false第 227 行on(this._container, wheel, this._manualScroll, { passive: true })——容器上的滚轮监听则声明为 passive第 596 行卸载时off(this._container, wheel, this._manualScroll, { passive: true })与注册时参数保持一致。从源码结构看passive: false用在mousemove/touchmove上是有明确用途的_handleMoveEvent 会在触摸设备上对事件调用evt.preventDefault()源码注释写明其目的——Prevent auto-refresh for when pulling down on touch devices防止触摸端下拉刷新以及阻止 Safari 在 viselect 接管滚动时的浏览器自动滚动。这正是规则文档中自定义滑动手势需要preventDefault()场景的实例如果这里误加了passive: true下拉刷新拦截将静默失效。而滚轮监听_manualScroll只负责把滚轮事件转换为选择区的平移不需要阻止默认滚动因此标注passive: true让页面滚动保持即时。统一的 on/off 事件工具与测试验证SelectionArea并没有直接裸调 DOM API而是使用 events.ts 提供的on/off工具。该工具把「元素单个、数组、HTMLCollection、NodeList」与「事件名单个或数组」统一归一化后对每个组合调用elmethod即默认capture: false并把调用方传入的 options例如{ passive: true }原样透传给addEventListener。对应的单元测试 events.spec.ts 明确验证了 passive 选项的透传行为on(parent.children, [mousedown, mouseup], listener, { passive: true }); expect(firstSpy).toHaveBeenCalledWith(mousedown, listener, { capture: false, passive: true, });这组测试同时覆盖了工具的另一半能力对 touch/mouse 事件的坐标归一化simplifyEvent将touches[0].clientX/clientY或clientX/clientY统一为{ x, y }这也是选择区需要同时响应鼠标与触摸的原因。Plate 仓库中的第二处落地被动特性检测useOnClickOutside.ts 展示了处理 passive 监听时容易忽略的一个工程细节并非所有环境都支持 options 对象形式仓库里先做了特性检测再决定注册参数。文件头部的canUsePassiveEvents()用Object.defineProperty构造了一个带passivegetter 的 options 对象并试探性注册事件若 getter 被浏览器触发则说明支持 passivelet passive false; const options Object.defineProperty({}, passive, { get() { passive true; }, }); const noop () null; window.addEventListener(test, noop, options); window.removeEventListener(test, noop, options); return passive;随后getEventOptions()对事件类型做区分第 68-69 行const getEventOptions (type: string): { passive: boolean } | boolean type.includes(touch) canUsePassiveEvents() ? { passive: true } : false;即只对含touch的事件类型、且环境支持 passive 时传{ passive: true }其余情况传false。这个 hook 默认监听mousedown与touchstart第 76 行其中touchstart正是需要 passive 以避免触摸延迟的类型而mousedown不涉及滚动则不标注。在 第 147 行 注册、第 130-134 行 卸载时两边使用完全相同的getEventOptions(type)结果保证注册与清理参数一致。这一实现呼应了规则文档的边界useOnClickOutside的回调只负责触发点击外部逻辑从不调用preventDefault()所以可以放心 passive而特性检测则保证了在不支持 options 对象的旧环境中不会因传入{ passive: true }而报错。与不要 passive场景的对照自定义滚动手势Plate 的图片预览组件 useImagePreview.ts 提供了一个反向参照其wheel监听第 58 行document.addEventListener(wheel, wheel)用于实现预览窗口的自定义平移——从源码结构看该 handler 读取deltaX/deltaY结合边界钳制计算偏移量再写入ImagePreviewStore的translate。这正是规则文档列出的custom zoom controls / swipe gestures一类监听器承载的是自定义手势语义一旦未来需要preventDefault()拦截页面滚动passive 标注就会让该调用失效。对于此类监听器显式{ passive: false }或保持默认非 passive 都是符合规则的做法。此外toc 包的 useContentController 中为滚动容器注册的scroll监听第 100 行属于典型的纯观察型监听——scroll事件本身没有可阻止的默认滚动行为不涉及preventDefault()语义是只观察不干预监听器的常态示例。实操要点小结对touchstart/touchmove/wheel的观察型监听埋点、日志、状态同步注册时加{ passive: true }让浏览器不再等待监听器返回即可推进滚动对需要preventDefault()的手势类监听滑动手势、自定义缩放/平移、阻止下拉刷新显式传{ passive: false }把意图写清楚参考 SelectionArea.ts 中对mousemove/touchmove的写法清理监听器时持有与注册时相同的函数引用如果团队封装了on/off之类的工具注册与卸载应传入一致的 options可参考 events.ts 及其 测试 的做法在需要兼容较老环境的通用工具中注册前先做 passive 特性检测参考 useOnClickOutside.ts 的canUsePassiveEvents()实现判定口诀与原始规则一致监听器可能调用preventDefault()就不许 passive否则尽量 passive——这条二分标准覆盖了 Plate 仓库中上述所有 touch/wheel 监听的注册决策。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表