目录
一、先给一个面试里的标准回答
二、什么叫“进入可视区”?
三、方式一:getBoundingClientRect()
1. 原理
2. 最简单判断
3. Vue 3 示例
4. Vue 2 示例
四、这种方式的优缺点
优点
缺点
五、方式二:IntersectionObserver
1. 原理
2. Vue 3 示例
3. 参数解释
root
threshold
rootMargin
六、IntersectionObserver 的优缺点
优点
缺点
七、实际项目里怎么选?
1. 简单页面 / 兼容要求高
2. 图片懒加载 / 曝光埋点 / 长列表
3. 在 Vue 项目里封装成指令或 Hook
八、Vue 中如何封装成可复用能力?
方案1:封装成自定义 Hook(Vue 3)
方案2:封装成自定义指令
九、如果面试官问:怎么做图片懒加载?
十、如果面试官问:滚动监听方案怎么优化?
可以从这几个角度回答
1. 节流
2. requestAnimationFrame
3. 减少 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 ref="boxRef" class="box">观察我是否进入可视区</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) }) </script>4. Vue 2 示例
<template> <div ref="box" class="box">观察我</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 ref="targetRef" class="box">观察我是否进入可视区</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() } }) </script>3. 参数解释
root
观察的根容器:
null:表示浏览器视口- 某个滚动容器元素:表示相对于该容器判断可视性
threshold
阈值,表示可见比例达到多少时触发:
0:露出一点就触发0.5:可见一半触发1:完全进入才触发
rootMargin
根容器的外扩或内缩边距:
rootMargin: '100px 0px'表示可以提前 100px 触发,常用于图片预加载。
六、IntersectionObserver的优缺点
优点
- 性能更好
- 不需要手动频繁监听滚动
- 语义清晰
- 特别适合懒加载、曝光埋点、无限滚动
缺点
- 老旧浏览器兼容性不如传统方案
- 某些复杂业务场景需要额外处理
- 需要理解阈值和根容器概念
七、实际项目里怎么选?
这个问题回答出来会很加分。
1. 简单页面 / 兼容要求高
用:
getBoundingClientRect() + scroll例如:
- 简单吸顶
- 判断某个模块是否露出
- 兼容老项目
2. 图片懒加载 / 曝光埋点 / 长列表
用:
IntersectionObserver例如:
- 图片进入视口才加载
- 广告曝光统计
- feed 流卡片曝光
- 无限滚动加载更多
3. 在 Vue 项目里封装成指令或 Hook
这是更工程化的做法。
八、Vue 中如何封装成可复用能力?
这部分特别适合面试加分。
方案1:封装成自定义 Hook(Vue 3)
import { 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 ref="boxRef">内容</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-view="handleView">内容</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 查询
不要每次滚动都重新querySelector
4. 批量处理
多元素场景不要反复读写 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。