效果
HTML部分(为了效果整得有点多)
思路:
- 为每个元素绑定 ref 和 :class,
- 初始化一个 visibleItems 数组,值为布尔值
- 通过setBoxRef来获取dom元素,
- 使用
IntersectionObserver
检测每个元素是否进入视口,并在进入视口时设置visibleItems
的值为true
- 定义
visible
类和关键帧动画@keyframes
来实现元素的进入动画。
<template>
<div class="container">
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[0] }"
style="opacity: 0"
>
<h4>字体</h4>
<div class="lbt_content scontent">1</div>
</div>
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[1] }"
style="opacity: 0"
>
<h4>轮播图</h4>
<div class="lbt_content scontent"></div>
</div>
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[2] }"
style="opacity: 0"
>
<h4>轮播图</h4>
<div class="lbt_content scontent"></div>
</div>
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[3] }"
style="opacity: 0"
>
<h4>轮播图</h4>
<div class="lbt_content scontent"></div>
</div>
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[4] }"
style="opacity: 0"
>
<h4>轮播图</h4>
<div class="lbt_content scontent">123</div>
</div>
<div
class="lbt content"
:ref="setBoxRef"
:class="{ visible: visibleItems[5] }"
style="opacity: 0"
>
<h4>轮播图</h4>
<div class="lbt_content scontent">1231</div>
</div>
</div>
</template>
TypeScript部分
<script setup lang="ts">
import { onMounted, ref, reactive } from "vue";
const visibleItems = reactive([]);
/* 保存观察dom元素 */
const boxrefList = reactive([]);
/* 获取要观察的dom元素 */
const setBoxRef = (el: any) => {
if (el) {
boxrefList.push(el);
}
};
const observeElements = () => {
const options: any = {
root: null, // 视口为浏览器窗口
rootMargin: "0px",
threshold: 0, // 当元素 0% 可见时触发
};
const observer = new IntersectionObserver((entries) => {
/*
*遍历IntersectionObserverEntry对象
*每个 IntersectionObserverEntry 对象包含有关目标元素与视口或其祖先元素的交叉状态的信息
*/
entries.forEach((entry) => {
/* 查找entry.target在boxrefList中的索引 */
const index = Array.from(boxrefList).indexOf(entry.target);
/* entry.isIntersecting 为一个布尔值,表示目标元素是否与视口或其祖先元素相交 */
if (entry.isIntersecting) {
/* 如果 entry.isIntersecting 为 true,则将 visibleItems 对应索引位置的值设为 true,表示该元素在视口中 */
visibleItems[index] = true;
} else {
/* 如果 entry.isIntersecting 为 false,则将 visibleItems 对应索引位置的值设为 false,表示该元素不在视口中 */
visibleItems[index] = false;
}
});
}, options);
/* 遍历需要观察的元素,然后通过observer 来观察box元素 */
boxrefList.forEach((box) => {
observer.observe(box);
});
};
onMounted(() => {
observeElements();
});
</script>
css部分
.container {
.content {
width: 100%;
margin-bottom: 50px;
background: white;
box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.3);
padding: 10px;
box-sizing: border-box;
border-radius: 20px;
.scontent {
height: 700px;
}
}
.visible {
opacity: 1;
transform-origin: bottom center;
animation: enter-animation 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* 动画 */
@keyframes enter-animation {
0% {
transform: scale(0.95) translateY(40px);
opacity: 0;
}
100% {
transform: scale(1) translateY(0);
opacity: 1;
}
}
}