交叉观察器 API(Intersection Observer API)提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。
下面这些情况都需要用到相交检测:
- 在页面滚动时“懒加载”图像或其他内容。
- 实现“无限滚动”网站,在滚动过程中加载和显示越来越多的内容,这样用户就不必翻页了。
- 报告广告的可见度,以便计算广告收入。
- 根据用户是否能看到结果来决定是否执行任务或动画进程。
IntersectionObserver options
- root
用作视口的元素,用于检查目标的可见性。必须是目标的祖先。如果未指定或为
null
,则默认为浏览器视口。
- rootMargin
根周围的边距。其值可以类似于 CSS margin 属性,例如
"10px 20px 30px 40px"
(上、右、下、左)。这些值可以是百分比。在计算交叉点之前,这组值用于增大或缩小根元素边框的每一侧。默认值为全零。
- threshold
一个数字或一个数字数组,表示目标可见度达到多少百分比时,观察器的回调就应该执行。如果只想在能见度超过 50% 时检测,可以使用 0.5 的值。如果希望每次能见度超过 25% 时都执行回调,则需要指定数组 [0, 0.25, 0.5, 0.75, 1]。默认值为 0(这意味着只要有一个像素可见,回调就会运行)。值为 1.0 意味着在每个像素都可见之前,阈值不会被认为已通过。
IntersectionObserver callback
每当目标满足该
IntersectionObserver
指定的阈值(threshold),回调被调用。
创建观察器
通过调用 IntersectionObserver 构造函数,创建交叉观测器,并将回调函数传给它,当一个方向或另一个方向越过阈值时,就运行该函数:
let options = {
root: document.querySelector(".wall-card"),
rootMargin: "0px",
threshold: 1.0,
};
const callback = () => {
// 如果不是相交,则直接返回
if (!entries[0].isIntersecting) return
// ...
}
let observer = new IntersectionObserver(callback, options);
添加观察者
observer.observe(boxChild);
IntersectionObserver的应用
-
碰撞
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cannon</title>
</head>
<style>
body,
html {
position: relative;
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
}
.box {
position: absolute;
z-index: 5;
top: 70vh;
right: 80vw;
width: 40px;
height: 40px;
animation: moveX 2.5s linear;
}
.box-child {
width: 100%;
height: 100%;
background: red;
border-radius: 50%;
animation: moveY 2.5s cubic-bezier(0, 0.5, 0.5, 1);
}
@keyframes moveX {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100vw);
}
}
@keyframes moveY {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-65vh);
}
}
@keyframes moveHalfY {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-40vh);
}
}
@keyframes opacity {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.circle {
position: absolute;
top: 0;
left: 0;
padding: 5px;
line-height: 18px;
font-size: 12px;
white-space: nowrap;
text-align: center;
color: #fff;
border-radius: 5px;
background: #FF5D5D;
animation: opacity 0.5s linear;
}
.wall-card {
position: fixed;
top: 20%;
right: 5%;
width: 15px;
height: 200px;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.8);
}
</style>
<body>
<div class="wall-card"></div>
<script>
let count = 0;
function getMaxzIndex() {
// 获取所有元素中的最高层级z-index
let highestZIndex = 0;
const elements = document.querySelectorAll('*'); // 选择所有元素
elements.forEach(element => {
const zIndex = window.getComputedStyle(element).zIndex;
if (zIndex !== 'auto' && zIndex > highestZIndex) {
highestZIndex = parseInt(zIndex);
}
});
return highestZIndex;
}
function createExplodeHtml(val) {
const { top, left } = val,
newHtml = document.createElement('div');
newHtml.className = 'circle';
newHtml.style.zIndex = getMaxzIndex() + 1;
newHtml.style.top = top - 15 + 'px';
newHtml.style.left = left - 15 + 'px';
newHtml.innerHTML = '撞到了...';
document.body.appendChild(newHtml);
newHtml.addEventListener('animationend', (e) => {
newHtml.remove();
})
}
var observer = new IntersectionObserver((entries, observer) => {
// 如果不是相交,则直接返回
if (!entries[0].isIntersecting) return
let IntersectInfo = entries[0].intersectionRect,
target = entries[0].target;
createExplodeHtml(IntersectInfo);
target.parentNode.remove();
}, {
root: document.querySelector(".wall-card"),
threshold: 0
});
timerId = setInterval(() => {
count++;
const box = document.createElement('div');
box.className = 'box';
const boxChild = document.createElement('div');
boxChild.className = 'box-child';
box.appendChild(boxChild);
document.querySelector('.wall-card').appendChild(box);
const randomNum = (Math.random() * (1.5 - 0.5) + 0.5).toFixed(2);
boxChild.style.animationTimingFunction = `cubic-bezier(0,${randomNum}, 0.5, 1)`;
boxChild.style.animationName = count % 3 ? 'moveY' : 'moveHalfY';
// 添加检察元素
observer.observe(boxChild);
boxChild.addEventListener('animationend', (e) => {
box.remove();
})
}, 500)
</script>
</body>
</html>
- 滚动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
.container {
height: 200vh;
}
.box {
height: 100vh;
background-color: #ccc;
margin: 10px;
}
.active {
background-color: red;
}
</style>
<div class="container">
<div class="box" id="box1">box1</div>
<div class="box" id="box2">box2</div>
<div class="box" id="box3">box3</div>
<div class="box" id="box4">box4</div>
<div class="box" id="box5">box5</div>
<div class="box" id="box6">box6</div>
<div class="box" id="box7">box7</div>
<div class="box" id="box8">box8</div>
</div>
<script>
const boxs = document.querySelectorAll('.box');
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
} else {
entry.target.classList.remove('active');
}
})
}, options);
boxs.forEach(box => {
observer.observe(box);
})
</script>
</body>
</html>