这个需求可以使用 Intersection Observer API 结合 Set
数据结构 来实现。IntersectionObserver
用于监听元素是否进入视口,而 Set
记录已经触发 alert
的元素,确保每个方块只触发一次。
实现思路
-
使用
IntersectionObserver
监听方块是否进入视口。 -
使用
Set
记录已经触发alert
的方块 ID,防止重复触发。 -
当一个方块第一次进入视口时,触发
alert
并将其 ID 记录到Set
中。 -
后续再次进入视口不会触发
alert
。
完整代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>方块进入视口触发 alert</title>
<style>
body {
height: 200vh; /* 让页面足够长以产生滚动 */
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 100%;
max-width: 600px;
height: 400px;
overflow-y: auto;
border: 2px solid black;
position: relative;
}
.box {
width: 100px;
height: 100px;
background-color: lightblue;
margin: 20px auto;
text-align: center;
line-height: 100px;
font-weight: bold;
border: 1px solid #000;
}
</style>
</head>
<body>
<h2>滚动查看方块</h2>
<div class="container">
<div id="box1" class="box">1</div>
<div id="box2" class="box">2</div>
<div id="box3" class="box">3</div>
<div id="box4" class="box">4</div>
<div id="box5" class="box">5</div>
<div id="box6" class="box">6</div>
</div>
<script>
// 记录已经触发过 alert 的元素 ID
const alertedElements = new Set();
// 观察器回调函数
function handleIntersection(entries, observer) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 元素进入视口
const id = entry.target.id;
if (!alertedElements.has(id)) {
alertedElements.add(id); // 记录已经触发 alert 的元素
alert(`方块 ${id} 进入视口`);
}
}
});
}
// 创建 IntersectionObserver
const observer = new IntersectionObserver(handleIntersection, {
root: document.querySelector('.container'), // 视口为滚动容器
rootMargin: '0px',
threshold: 0.5, // 50% 可见时触发
});
// 观察所有 .box 元素
document.querySelectorAll('.box').forEach((box) => observer.observe(box));
</script>
</body>
</html>
代码解析
-
设置滚动容器:
-
div.container
作为滚动区域,包含多个.box
小方块。 -
height: 400px; overflow-y: auto;
让container
可以滚动。
-
-
创建
IntersectionObserver
:-
root: document.querySelector(".container")
→ 设定滚动视口为.container
。 -
threshold: 0.5
→ 当方块 50% 进入视口时触发。
-
-
使用
Set
记录已触发的元素:-
alertedElements.add(id);
记录已经触发 alert 的元素,防止重复触发。 -
每个
.box
进入视口时,会先检查是否已经在Set
里,不在才alert
。
-
优化
1. 只让 alert
触发一次,并取消观察
如果方块进入视口后不再需要观察,可以 触发 alert
后取消监听:
function handleIntersection(entries, observer) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.id;
if (!alertedElements.has(id)) {
alertedElements.add(id);
alert(`方块 ${id} 进入视口`);
observer.unobserve(entry.target); // 取消监听
}
}
});
}
这样可以 提高性能,避免不必要的回调触发。
适用场景
✅ 页面滚动时,元素进入视口一次后触发事件(如 alert
、动画等)。
✅ 适用于滚动容器或整个页面滚动。
✅ 可扩展性高,可以改为 触发 API 请求、统计用户行为 等。
其他方案
1. getBoundingClientRect()
+ 监听 scroll
事件
实现方式:
- 监听
scroll
事件,每次滚动时计算.box
的位置,看它是否进入视口。
缺点:
❌ 性能较差,滚动时会不断触发 scroll
,需要 throttle
优化。
❌ 兼容性较好,但 代码复杂,不如 IntersectionObserver
简洁。
结论
方案 | 优点 | 缺点 |
---|---|---|
Intersection Observer | 性能好,代码简洁,现代浏览器支持 | 旧版 IE 不支持(需 polyfill) |
scroll 事件 + getBoundingClientRect() | 兼容性好 | 代码复杂,性能较差 |
最佳选择
如果浏览器支持 Intersection Observer,推荐 使用 Intersection Observer,性能好、代码简洁!🎯