35、Vue 中如何判断元素进入可视区?
目录一、先给一个面试里的标准回答二、什么叫“进入可视区”三、方式一getBoundingClientRect()1. 原理2. 最简单判断3. Vue 3 示例4. Vue 2 示例四、这种方式的优缺点优点缺点五、方式二IntersectionObserver1. 原理2. Vue 3 示例3. 参数解释rootthresholdrootMargin六、IntersectionObserver 的优缺点优点缺点七、实际项目里怎么选1. 简单页面 / 兼容要求高2. 图片懒加载 / 曝光埋点 / 长列表3. 在 Vue 项目里封装成指令或 Hook八、Vue 中如何封装成可复用能力方案1封装成自定义 HookVue 3方案2封装成自定义指令九、如果面试官问怎么做图片懒加载十、如果面试官问滚动监听方案怎么优化可以从这几个角度回答1. 节流2. requestAnimationFrame3. 减少 DOM 查询4. 批量处理5. 优先使用 IntersectionObserver十一、面试中怎么回答才算精彩高分回答模板十二、一个更适合背诵的精简版十三、顺手给你一个最常见判断公式只判断纵向进入可视区判断完整进入可视区十四、一句话总结这是一个很常见的前端 / Vue 面试题。如果你只回答用getBoundingClientRect()不能算错但不够完整。因为面试官通常想听的是有哪些判断方式在 Vue 里怎么拿到元素如何处理滚动监听有没有更现代的方案哪种方案性能更好实际项目里怎么选一、先给一个面试里的标准回答在 Vue 中判断元素是否进入可视区常见有两种方式一种是通过ref获取 DOM 元素然后用getBoundingClientRect()判断元素的top、bottom是否和视口区域有交集另一种是使用IntersectionObserver让浏览器帮我们监听元素和可视区的相交情况。如果是简单场景或者需要兼容老环境我会用getBoundingClientRect() scroll/resize如果是图片懒加载、曝光埋点、长列表可见性判断这类场景我更推荐IntersectionObserver因为性能更好也不需要手动频繁监听滚动事件。在 Vue 里实现时通常会通过ref拿元素在mounted/onMounted中初始化在组件卸载时移除监听或断开 observer。这段已经算是比较完整的面试答案了。二、什么叫“进入可视区”先把概念说清楚这样回答会更严谨。通常“进入可视区”指的是元素和当前视口区域发生了相交用户有机会看到它。这里又分几种业务定义只要露出一点点就算进入必须整个元素都进入进入超过 50% 才算进入后停留一段时间才算曝光所以面试时最好补一句是否进入可视区本质上取决于业务定义。这样会显得你不是在死背 API。三、方式一getBoundingClientRect()这是最基础、最常见的方案。1. 原理getBoundingClientRect()可以拿到元素相对于当前视口的位置topbottomleftrightwidthheight只要元素和视口范围有交集就可以认为它进入可视区。2. 最简单判断const rect el.getBoundingClientRect() const inView rect.top window.innerHeight rect.bottom 0这个判断的意思是元素顶部在视口底部上方元素底部在视口顶部下方说明元素和视口纵向有交集。如果还要判断横向const inView rect.top window.innerHeight rect.bottom 0 rect.left window.innerWidth rect.right 03. Vue 3 示例template div refboxRef classbox观察我是否进入可视区/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue const boxRef ref(null) const checkInView () { const el boxRef.value if (!el) return const rect el.getBoundingClientRect() const inView rect.top window.innerHeight rect.bottom 0 console.log(是否进入可视区:, inView) } onMounted(() { checkInView() window.addEventListener(scroll, checkInView) window.addEventListener(resize, checkInView) }) onBeforeUnmount(() { window.removeEventListener(scroll, checkInView) window.removeEventListener(resize, checkInView) }) /script4. Vue 2 示例template div refbox classbox观察我/div /template script export default { methods: { checkInView() { const rect this.$refs.box.getBoundingClientRect() const inView rect.top window.innerHeight rect.bottom 0 console.log(inView) } }, mounted() { this.checkInView() window.addEventListener(scroll, this.checkInView) window.addEventListener(resize, this.checkInView) }, beforeDestroy() { window.removeEventListener(scroll, this.checkInView) window.removeEventListener(resize, this.checkInView) } } /script四、这种方式的优缺点优点兼容性好实现简单理解成本低灵活能自定义各种可见规则缺点需要手动监听scroll/resize高频触发时可能影响性能页面元素很多时逐个计算成本较高代码相对繁琐五、方式二IntersectionObserver这是现代浏览器里更推荐的方案。1. 原理IntersectionObserver会监听目标元素和根容器默认是视口之间的相交情况当元素进入或离开可视区时浏览器会回调通知你。它不需要你自己在scroll事件里不停计算性能更好。2. Vue 3 示例template div reftargetRef classbox观察我是否进入可视区/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue const targetRef ref(null) let observer null onMounted(() { observer new IntersectionObserver((entries) { entries.forEach(entry { console.log(是否进入可视区:, entry.isIntersecting) console.log(可见比例:, entry.intersectionRatio) }) }, { root: null, // 默认视口 threshold: 0.1 // 可见 10% 就触发 }) if (targetRef.value) { observer.observe(targetRef.value) } }) onBeforeUnmount(() { if (observer) { observer.disconnect() } }) /script3. 参数解释root观察的根容器null表示浏览器视口某个滚动容器元素表示相对于该容器判断可视性threshold阈值表示可见比例达到多少时触发0露出一点就触发0.5可见一半触发1完全进入才触发rootMargin根容器的外扩或内缩边距rootMargin: 100px 0px表示可以提前 100px 触发常用于图片预加载。六、IntersectionObserver的优缺点优点性能更好不需要手动频繁监听滚动语义清晰特别适合懒加载、曝光埋点、无限滚动缺点老旧浏览器兼容性不如传统方案某些复杂业务场景需要额外处理需要理解阈值和根容器概念七、实际项目里怎么选这个问题回答出来会很加分。1. 简单页面 / 兼容要求高用getBoundingClientRect() scroll例如简单吸顶判断某个模块是否露出兼容老项目2. 图片懒加载 / 曝光埋点 / 长列表用IntersectionObserver例如图片进入视口才加载广告曝光统计feed 流卡片曝光无限滚动加载更多3. 在 Vue 项目里封装成指令或 Hook这是更工程化的做法。八、Vue 中如何封装成可复用能力这部分特别适合面试加分。方案1封装成自定义 HookVue 3import { ref, onMounted, onBeforeUnmount } from vue export function useInView(targetRef) { const isInView ref(false) let observer null onMounted(() { observer new IntersectionObserver(([entry]) { isInView.value entry.isIntersecting }) if (targetRef.value) { observer.observe(targetRef.value) } }) onBeforeUnmount(() { observer observer.disconnect() }) return { isInView } }使用template div refboxRef内容/div p{{ isInView ? 可见 : 不可见 }}/p /template script setup import { ref } from vue import { useInView } from ./useInView const boxRef ref(null) const { isInView } useInView(boxRef) /script方案2封装成自定义指令export default { mounted(el, binding) { const observer new IntersectionObserver(([entry]) { if (binding.value) { binding.value(entry.isIntersecting, entry) } }) observer.observe(el) el._observer observer }, unmounted(el) { el._observer el._observer.disconnect() } }使用template div v-in-viewhandleView内容/div /template script setup const handleView (visible, entry) { console.log(是否可见:, visible) } /script九、如果面试官问怎么做图片懒加载你可以顺带回答在 Vue 里做图片懒加载我通常会用IntersectionObserver。先给图片一个占位图监听图片元素是否进入可视区当entry.isIntersecting为true时再把真实图片地址赋值给src加载完成后取消监听。这种方式比滚动事件 getBoundingClientRect()更高效。十、如果面试官问滚动监听方案怎么优化这是追问高频点。可以从这几个角度回答1. 节流滚动事件触发很频繁可以节流function throttle(fn, delay) { let timer null return function () { if (timer) return timer setTimeout(() { fn.apply(this, arguments) timer null }, delay) } }2.requestAnimationFrame把高频滚动计算合并到一帧里执行。3. 减少 DOM 查询不要每次滚动都重新querySelector4. 批量处理多元素场景不要反复读写 DOM尽量统一计算5. 优先使用IntersectionObserver更省性能十一、面试中怎么回答才算精彩精彩的关键不是“说 API 多”而是先说主流方案再说适用场景再说性能和工程化高分回答模板在 Vue 中判断元素是否进入可视区常见有两种做法。第一种是通过ref获取 DOM 元素在mounted/onMounted里使用getBoundingClientRect()获取元素相对于视口的位置再结合window.innerHeight、window.innerWidth判断元素是否和视口有交集。这种方式兼容性好但通常需要配合scroll和resize事件监听高频场景下要注意节流。第二种是使用IntersectionObserver它可以让浏览器直接监听元素和可视区的相交状态不需要手动在滚动事件里频繁计算性能更好也更适合图片懒加载、曝光埋点和长列表场景。在实际 Vue 项目中我通常会通过ref拿元素在组件挂载时初始化监听在组件卸载时移除监听或disconnectobserver。如果这个能力要复用我会进一步封装成自定义指令或者 Composition API Hook。所以如果是简单场景我会用getBoundingClientRect()如果是生产项目里大量元素的可视区监听我更倾向于IntersectionObserver。这段答出来已经很不错了。十二、一个更适合背诵的精简版Vue 中判断元素进入可视区通常有两种方式一种是ref getBoundingClientRect()通过判断元素的top/bottom是否和视口范围有交集另一种是IntersectionObserver直接监听元素是否进入视口。前者实现简单、兼容性好但需要自己监听滚动事件后者性能更好更适合懒加载和曝光埋点。在 Vue 中我一般会在mounted/onMounted中初始化在组件卸载时清理监听。十三、顺手给你一个最常见判断公式只判断纵向进入可视区const rect el.getBoundingClientRect() const inView rect.top window.innerHeight rect.bottom 0判断完整进入可视区const rect el.getBoundingClientRect() const fullyInView rect.top 0 rect.left 0 rect.bottom window.innerHeight rect.right window.innerWidth十四、一句话总结Vue 中判断元素进入可视区基础方案是ref getBoundingClientRect()更推荐的现代方案是IntersectionObserver。