ARTICLE DETAIL

资讯详情

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

Papermark 滚动性能优化指南:为 React 触摸与滚轮事件正确使用 Passive Event Listeners

Papermark 滚动性能优化指南:为 React 触摸与滚轮事件正确使用 Passive Event Listeners 后端前端企业应用【免费下载链接】papermarkPapermark is the open-source DocSend alternative and secure data rooms with built-in analytics and custom domains.项目地址https://gitcode.com/GitHub_Trending/pa/papermark点击查看免费下载在 Papermark 这类以文档预览、数据室浏览为核心体验的开源项目中滚动流畅度直接影响用户的阅读观感。本文以 Vercel React 最佳实践规则client-passive-event-listeners为骨架讲解为何touchstart/wheel等监听器会造成滚动延迟、如何通过{ passive: true }恢复即时滚动并结合 Papermark 仓库内实际的 viewer 组件源码如触摸缩放、滑动翻页、面板定位等给出可直接复用的正反示例与判定标准。为什么滚动会被事件监听器拖慢passive 的浏览器机制浏览器在处理触摸touch和滚轮wheel事件时默认处于不确定该监听器是否会调用preventDefault()的状态。为了保住取消默认行为的可能性主线程必须先完整执行完所有监听器回调才能决定是否执行滚动。当页面上挂着多个 touch/wheel 监听器、且回调内部存在日志、埋点上报等耗时操作时每次滚动都会被强制排队等待表现为明显的卡顿与延迟起滚。addEventListener的第三个参数传入{ passive: true }后浏览器会明确收到该监听器绝不会调用preventDefault()的承诺从而跳过等待、立刻开始滚动。这就是 passive listener 消除滚动延迟的核心原理。规则文件 client-passive-event-listeners.md 将此类问题的 impact 标注为 MEDIUM因为它只影响滚动相关的交互路径但一旦中招体感非常明显。需要特别指出的是React 17 之后的合成事件synthetic events默认就是 passive 的所以上述问题主要出现在通过原生document/window/ DOM 元素直接addEventListener挂载的监听器上——这正是 Papermark 的 viewer 组件大量采用原生监听的原因也是本文讨论的场景。错误写法与正确写法对照规则文档给出了同一段滚动位置上报代码的正反两种实现。错误写法未声明 passive浏览器被迫等待每个监听器执行完毕useEffect(() { 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) } }, [])正确写法声明 passive滚动立即生效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) } }, [])两点实践细节值得注意removeEventListener时无需重复传入passive选项——浏览器根据注册时的 capture 标志匹配监听器因此清理函数写法不变见上例。回调中如果确实只需要touches[0].clientX、deltaY这类只读数据埋点、日志、统计分析就完全符合 passive 的使用前提一旦回调可能调用preventDefault()则必须改回非 passive 并承担相应开销。何时使用 passivetracking 与日志类监听器规则文档明确了适用边界应当使用{ passive: true }的场景——所有不调用preventDefault()的监听器访问/浏览埋点tracking / analytics日志记录logging纯状态同步与 UI 联动如滚动位置驱动的面板定位严禁使用 passive 的场景——所有必须调用preventDefault()才能工作的监听器自定义滑动swipe手势自定义缩放zoom控件需要拦截浏览器默认触摸/滚轮行为的任何逻辑判定方法很简单回调体内出现e.preventDefault()就不能 passive没有就应该 passive。仓库实测一面板定位监听器passive 的正确应用use-viewer-panel-top.ts 是 Papermark 中典型的只读监听它需要跟随window滚动实时计算右侧 AI 问答面板的top值使其贴合顶栏底部。该监听器全程不调用preventDefault()因此同时满足两个优化点const onScroll () { cancelAnimationFrame(frame); frame requestAnimationFrame(measure); }; measure(); window.addEventListener(scroll, onScroll, { passive: true }); window.addEventListener(resize, onScroll, { passive: true });这里passive: true消除了滚动等待requestAnimationFrame又将高频滚动回调合并到每一帧只执行一次measure()避免了对getBoundingClientRect()的重复强制同步布局。同类的只读滚动监听在仓库中还有多处例如use-at-bottom.ts 通过window.addEventListener(scroll, handleScroll, { passive: true })判断是否滚动到底部用于触发加载更多pages-vertical-viewer.tsx 在连续竖排文档阅读器中监听容器滚动结合 150ms 防抖判断滚动是否结束同样声明了passive: truefade-scroll-area.tsx 监听 Radix 滚动视口的scroll事件驱动顶部/底部渐隐遮罩的显隐并配合ResizeObserver处理内容异步加载导致的尺寸变化。仓库实测二滑动翻页监听器passive capture 组合横向翻页阅读器 pages-horizontal-viewer.tsx 展示了另一个典型模式通过触摸手势识别左右滑动翻页但翻页动作并不依赖preventDefault()——它只是在结束时通过位移阈值如Math.abs(dx) 48、dx/dy 比例判定方向再调用goToNextPageRef.current()const opts: AddEventListenerOptions { passive: true, capture: true }; el.addEventListener(touchstart, onStart, opts); el.addEventListener(touchmove, onMove, opts); el.addEventListener(touchend, onEnd, opts); return () { el.removeEventListener(touchstart, onStart, opts); el.removeEventListener(touchmove, onMove, opts); el.removeEventListener(touchend, onEnd, opts); };三个值得学习的细节{ passive: true }让touchmove高频事件不再阻塞滚动capture: true让监听器在捕获阶段提前介入保证手势判定的时序清理函数直接复用同一个opts对象确保 remove 时匹配到注册时的标志位避免内存泄漏。仓库实测三捏合缩放监听器必须非 passive 的反例与上文形成鲜明对比的是 use-touch-zoom.ts。Papermark 连续阅读器的双指捏合缩放必须拦截浏览器的原生捏合手势否则原生缩放会与阅读器自身的滚动容器互相冲突因此四个触摸事件全部以{ passive: false }注册并在回调中显式调用e.preventDefault()el.addEventListener(touchstart, onTouchStart, { passive: false }); el.addEventListener(touchmove, onTouchMove, { passive: false }); el.addEventListener(touchend, onTouchEnd, { passive: false }); el.addEventListener(touchcancel, onTouchEnd, { passive: false });文件头部的注释use-touch-zoom.ts还揭示了一个 React 生态的重要背景React 的合成触摸处理函数本身就是 passive 的无法preventDefault()浏览器手势因此这类需要拦截手势的场景只能绕开 React 合成事件、走原生addEventListener并主动声明passive: false。这正是何时不能用 passive的最佳实证。落地检查清单对照 Vercel 规则与本仓库实践给出一份可直接执行的审查清单在document、window或 DOM 元素上注册touchstart/touchmove/wheel原生监听器时默认优先考虑补上{ passive: true }回调体内若存在preventDefault()调用明确改为{ passive: false }并注释说明原因参考 use-touch-zoom.ts 的注释习惯便于后人审查埋点、日志、只读 UI 状态同步类监听器一律 passive并优先配合requestAnimationFrame/ 防抖节流避免在滚动回调中做重计算或强制同步布局若同时使用capture: true将选项对象提取为常量复用保证注册与清理参数一致始终在useEffect的清理函数中调用对应的removeEventListener防止滚动监听器在组件卸载后继续累积。遵循上述原则即可在 Papermark 这类重度依赖滚动交互的文档/数据室产品中既保留埋点分析与 UI 联动能力又让触摸与滚轮滚动保持即时响应不损失任何功能的前提下换来流畅的浏览体验。赞分享后端前端企业应用【免费下载链接】papermarkPapermark is the open-source DocSend alternative and secure data rooms with built-in analytics and custom domains.项目地址https://gitcode.com/GitHub_Trending/pa/papermark点击查看免费下载相关推荐Langfuse 前端性能优化为触摸与滚轮事件使用 Passive Event Listeners 消除滚动延迟Langfuse 前端性能优化为触摸与滚轮事件使用 Passive Event Listeners 消除滚动延迟 导读 本指南基于 langfuse 仓库内人工智能LLMOps可观测性AI 评测LLM 网关后端前端OpenMontage 前端性能规则解析为触摸与滚轮事件启用 Passive Event ListenersOpenMontage 前端性能规则解析为触摸与滚轮事件启用 Passive Event Listeners 导读 本文解析 OpenMontage 仓库中人工智能AI Agent音视频媒体生成工作流自动化React 滚动性能优化实战为 touch/wheel 事件启用 Passive Event ListenersReact 滚动性能优化实战为 touch/wheel 事件启用 Passive Event Listeners 本文围绕本仓库 .agents 技能库中 V前端教程上一篇tiny11builder实战指南轻松打造精简Windows 11镜像的完整解决方案下一篇AWS EKS Workshop 项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表