ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Quasar 的 useIntersection composable:基于 Intersection Observer 的可见性侦测完全指南

Quasar 的 useIntersection composable:基于 Intersection Observer 的可见性侦测完全指南 前端UI组件跨平台【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址https://gitcode.com/gh_mirrors/qu/quasar点击查看免费下载useIntersection()是 Quasar 提供的 setup 式组合式函数composable通过一个响应式布尔值isIntersecting告诉你某个元素是否出现在屏幕上或某个可滚动容器内底层基于浏览器原生 Intersection Observer APIv2.30 可用。它适合在需要把“元素可见性”直接变成组件状态、又不想为此多包一层 DOM 元素的场景例如图片懒加载、列表项入场动画、图表进入视口后再初始化等。读完本文你将掌握useIntersection()的完整 API、三种响应式传参方式、运行中动态修改配置的行为细节以及它与 Quasar 内部共享观察器池的实现原理。一、它是什么QIntersection 组件与 Intersection 指令的 setup 版对应物在 Quasar 生态中同一套“元素可见性侦测”能力有三个入口入口形态适用场景QIntersection 组件模板组件需要额外 DOM 包裹、按可见性渲染插槽内容Intersection 指令模板指令想在元素上直接挂回调、不引入新节点useIntersection()composablesetup 代码想直接拿到响应式状态作用在组件自身或任意元素/组件 ref 上三者共享同一个底层观察器池见下文源码解析因此同时观察大量元素时滚动开销依然很低。选择 composable 的典型理由正如官方文档所述把状态放在组件或任意元素/组件 ref上而不需要额外包裹一层 DOM 元素。[!TIP] 在 SSR 或 SSG 模式的服务器端composable 永远不会真正观察元素isIntersecting会一直保持false直到客户端接管渲染。二、语法与完整 API 签名JavaScript 用法import { useTemplateRef } from vue import { useIntersection } from quasar setup () { const target useTemplateRef(target) // 一个 Element 或组件 const { isIntersecting, refresh, stop } useIntersection({ // 以下均为可选参数 target, // 省略时观察组件自身的根元素 root: null, // 作为视口的元素null 表示浏览器视口 rootMargin: 0px, // 围绕 root 的 CSS 风格边距 threshold: 0, // Number 或 Number 数组取值 0 到 1 once: false, // 目标首次可见后停止观察 disabled: false, // 暂停观察 onIntersect (entry) { // 每次收到 IntersectionObserverEntry 时调用 // 返回 false 可停止观察 } }) // ... }TypeScript 类型签名function useIntersection( options?: MaybeRefOrGetter{ target?: MaybeRefOrGetter Element | ComponentPublicInstance | null | undefined root?: Element | Document | null rootMargin?: string threshold?: number | number[] once?: boolean disabled?: boolean onIntersect?: (entry: IntersectionObserverEntry) boolean | void } ): { isIntersecting: Refboolean refresh: () void stop: () void }三个返回值isIntersecting: Refboolean核心响应式状态跟随观察结果实时更新。refresh(): void让观察器立刻重新上报当前状态——无论状态是否变化。因为原生IntersectionObserver只在状态变化时触发回调当你对布局做了改动后需要拿到“新鲜判决”时例如想知道原本可见的目标在周围内容变高后是否仍然可见就要调用它。源码实现是调用reobserve(el)先unobserve再observe强制观察器重新投递一次 entry。stop(): void永久结束观察。通常用不到因为组件销毁时 composable 会自动释放onBeforeUnmount中调用release()stop()之后sub.done被置为true即使后续选项再变化也不会重新开始观察。三、target 的解析规则默认观察组件根元素不传target时composable 会从组件挂载完成那一刻起观察调用它的组件自身的根元素。源码中这一逻辑位于 getElement 函数function getElement(target, vm) { const value target void 0 ? (vm null ? null : vm.proxy.$el) : toValue(target) if (value null || value void 0) return null // 组件 ref 会解析到它的根元素fragment 根文本/注释节点无法被观察 const el value.$el ?? value return el.nodeType 1 ? el : null }几个值得注意的细节传target时支持MaybeRefOrGetter即可以传普通 Element、ref 或 getter传组件 ref时会自动解包到它的根元素value.$el最终只接受nodeType 1的真实元素节点文本节点、注释节点会被判为null渲染 fragment多个根节点的组件没有可观察的根元素此时必须显式提供target。这一点同样有测试佐证does not observe a fragment root用例断言挂载一个渲染两个div的组件时观察器数量为 0。首次观察发生在onMounted钩子中effect.run()确保此时模板 ref 与根元素都已就绪vm null的非组件上下文如普通函数中调用则立即执行。此外 composable 内部使用ReactiveEffect而非开销更高的watch来追踪 options 读取到的全部响应式状态这也是它能在响应式变化时自动重新应用配置的底层基础。四、运行中动态修改选项Ref 与 getter 的响应式魔法选项既可以是普通 Object只读取一次也可以是 Ref 或 getter 函数。传 Ref/getter 时composable 会追踪选项内部读取的所有响应式状态并在这些状态变化时自动重新应用——你不需要手动调用任何“update”方法切换disabled暂停/恢复观察暂停期间isIntersecting保持最后一次的值修改root、rootMargin或threshold目标会被转移到使用新配置的观察器把target指向另一个元素或模板 ref 因v-if而更换观察目标会跟着切换替换onIntersect从下一条 entry 开始生效。如果解析出的选项值与之前完全一致则视为 no-op不会重建观察器——所以即使 getter 每次运行都重新组装一个 Object也不会带来额外开销。底层由 observe 函数 保证当root、rootMargin、threshold三者均未变化时直接return只有配置真正变化时才先unobserve再注册新观察器。测试reacts to option changes without re-registering equal ones验证了这一点getter 因无关状态重新求值但选项等值时观察器数量与 observe 调用次数均保持不变。import { ref } from vue import { useIntersection } from quasar setup () { const paused ref(false) const threshold ref(0) const { isIntersecting } useIntersection(() ({ disabled: paused.value, threshold: threshold.value })) function pause () { paused.value true } function resume () { paused.value false } // 从现在起只有当元素至少一半可见时 handler 才会触发 function requireHalf () { threshold.value 0.5 } // ... }同样地也可以把整个选项对象放进一个 Refconst options ref({ rootMargin: 0px }) const { isIntersecting } useIntersection(options) // 之后 options.value { rootMargin: 200px }一个重要的边界情况已经触发过的once观察是“一次性”的——之后无论怎么切换disabled或修改其他选项都不会重新开始观察。如果确实需要重新开始只能再次调用useIntersection()新建一个。这一行为在测试once stops after the first intersecting entry and never re-arms中明确验证once触发后即使把disabled先置true再置false观察器数量始终为 1不会重新创建。五、onIntersect 回调拿到原始 entry 数据onIntersect(entry)会在每一条entry 投递时被调用参数是原生IntersectionObserverEntry因此可以访问entry.intersectionRatio相交比例、entry.boundingClientRect目标矩形、entry.rootBoundsroot 矩形等原始数据。两个关键规则在回调执行前isIntersecting已经同步更新为新值测试calls onIntersect with every entry验证了这一点回调返回false即可停止观察。注意源码中once与onIntersect的判定顺序handler 先执行随后才判断sub.once true entry.isIntersecting来决定是否退役——也就是说让元素退役的那条相交 entry 一定会被回调先看到不会漏掉。六、实战示例图片懒加载官方文档给出的经典场景是懒加载图片在卡片即将进入视口前就开始加载配合once: true加载完成后即不再观察。template div classcard img v-ifisIntersecting :srcsrc / /div /template script setup import { useIntersection } from quasar defineProps({ src: String }) // 观察该组件自身的根元素卡片 const { isIntersecting } useIntersection({ rootMargin: 200px, once: true }) /scriptrootMargin: 200px把“可见区域”向外扩展 200px相当于提前 200px 开始加载once: true保证卡片一旦可见并触发渲染后就不再被继续观察节省后续滚动开销。七、实战示例观察指定元素并读取原始 entry如果需要观察某个具体元素或子组件就传入模板 ref需要原始 entry 数据相交比例、边界矩形时使用onIntersect钩子template div div refchartRef classchart / /div /template script setup import { useTemplateRef } from vue import { useIntersection } from quasar const chartRef useTemplateRef(chartRef) const { isIntersecting } useIntersection({ target: chartRef, threshold: [0, 0.5, 1], onIntersect(entry) { console.log(ratio, entry.intersectionRatio) } }) /scriptthreshold: [0, 0.5, 1]表示在元素可见比例分别达到 0%、50%、100% 时各触发一次回调适合需要精确感知元素“露出多少”的图表、进度动画等场景。八、源码原理共享观察器池让大量观察保持廉价整个 Quasar 的 Intersection 能力都建立在 ui/src/utils/private.intersection/intersection.js 这个私有观察器池之上useIntersection、Intersection 指令与 QIntersection 组件三处均复用它。为什么需要共享池源码注释解释得很清楚浏览器对每个IntersectionObserver实例每帧都要单独跑一遍计算N 个单元素观察器的扩展性远差于“一个观察器观察 N 个目标”。因此该模块按(root, rootMargin, threshold)三元组做键为每种配置只创建一个IntersectionObserver池中所有元素共享它const pools new Map() // root - Mapkey, pool // ... const key ${rootMargin}|${threshold} let pool byKey.get(key) if (pool void 0) { pool { root, rootMargin, threshold, key, count: 0, observer: new IntersectionObserver(onEntries, { root, rootMargin, threshold }) } byKey.set(key, pool) } pool.count核心要点引用计数管理observe()增加countunobserve()递减当count归零时调用observer.disconnect()并把该池从 Map 中移除避免内存泄漏见 unobserve 函数。订阅者模型每个订阅者sub在同一时刻只观察一个元素池会在订阅者身上写入pool观察期间与done退役后两个标记done的订阅者永远不会再被观察。entry 分发防护onEntries会校验sub.pool.observer ! observer因为与MutationObserver不同disconnect()/unobserve()不会丢弃已排队的 entry——离开或迁移到其他观察器的元素仍可能收到旧观察器的 entry必须跳过。once 的退役逻辑sub.handler(entry) false或(sub.once true entry.isIntersecting)任一成立即sub.done true并解除观察。指令侧对比Intersection 指令ui/src/directives/intersection/Intersection.js的update同样把root/rootMargin/threshold归一化后交给observe()undefined/false值则直接unobserve()并清空 handlermodifiers.once被写入订阅者的once字段。可见三个 API 最终都汇聚到同一个池这正是“观察很多元素依然便宜”的工程根源。SSR 分支composable 源码开头的__QUASAR_SSR_SERVER__分支在服务端直接返回{ isIntersecting, stop: noop }配合文档中“SSR/SSG 下isIntersecting恒为false”的说明确保服务端渲染不会产生观察副作用。测试佐证use-intersection.test.js 覆盖了完整的契约——默认观察根元素、fragment 根不观察、target 解析、观察器选项透传、onIntersect返回false停止、once不重复武装、disabled暂停/恢复、等值选项不重建、target 切换跟随、stop()/refresh()语义、卸载自动释放其中[Generic]分组还会卸载 stub、改用浏览器真实IntersectionObserver做滚动进出视口的端到端验证元素滚入overflow: auto容器时isIntersecting由false变true滚出时再变回false。九、集成佐证QIntersection 组件正是建立在它之上QIntersection 组件 内部直接调用useIntersection并通过 getter 把自身的root/margin/threshold/once/disableprops 映射为 composable 选项const { isIntersecting: showing } useIntersection(() ({ root: props.root, rootMargin: props.margin, threshold: props.threshold, once: props.once, disabled: props.disable }))注释还提到一个工程细节禁用状态通过选项传递而非拆除被观察元素是为了避免重建插槽内容issue #12668。同时composables.js 入口 将use-intersection作为具名导出对外暴露import { useIntersection } from quasar即来源于此。这进一步印证掌握了useIntersection()也就掌握了 Quasar 整套 Intersection 能力的内核——无论是直接用 composable还是理解 QIntersection 组件/Intersection 指令的行为其底层契约完全一致。赞分享前端UI组件跨平台【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址https://gitcode.com/gh_mirrors/qu/quasar点击查看免费下载相关推荐react-use 的 useIntersection用 Intersection Observer API 感知元素可见性react use 的 useIntersection用 Intersection Observer API 感知元素可见性 导读 useIntersecti前端Vuetify v-intersect 指令详解基于 Intersection Observer 的视口可见性检测Vuetify v intersect 指令详解基于 Intersection Observer 的视口可见性检测 v intersect 是 Vuetify前端UI组件Alpine.js Intersect 插件完全指南基于 Intersection Observer 的视口检测与懒加载实战Alpine.js Intersect 插件完全指南基于 Intersection Observer 的视口检测与懒加载实战 导读 Alpine.js Int前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表