概述
最近发现很多网站都有滚动条滚动到可视区然后执行特定的动画,这样给访问的用户可以代码更好的用户体验,比如小米汽车的官网,当我们滚动到可视区的时候,会有文字或者图片从下方然后慢慢升起的过渡效果,很是炫酷,这里我们可以借助IntersectionObserver和Element.animate很方便实现类似的效果,我相信理解下面这个动画实现后,再去看其他的可视区动画效果的原理都能理解,万变不离其中。
最终效果
两个重要的工具
IntersectionObserver
IntersectionObserver
接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root),简单来说就是可以观察元素到文档可视区的变化(目前我们这里需要的)
Element.animate()
Element
接口的 animate()
方法是创建一个新的 Animation
的便捷方法,将它应用于元素,然后运行动画。它将返回一个新建的 Animation
对象实例,简单来说就是可以api调用方式追加元素动画,而不需要使用三方库,这是非常强大的,可已暂停,开始动画,大大方便了我们自定义动画的灵活变通。
实现
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
}
html,
body {
height: 100%;
}
.item {
width: 500px;
height: 200px;
background-color: pink;
margin: 10px;
text-align: center;
line-height: 200px;
}
.container-wrap {
display: flex;
justify-content: center;
}
</style>
<body>
<div class="container-wrap">
<ul class="container"></ul>
</div>
<script>
const domMap = new WeakMap();
// 随机生成颜色
function generateRandomColor() {
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
return `rgb(${r},${g},${b})`;
}
// 判断元素是否在可视区已经上面
function checkElementShowTopViewPort(dom) {
return (
dom.getBoundingClientRect().top <
document.documentElement.clientHeight
);
}
// 添加动画
function addAnimate(dom) {
const animate = dom.animate(
[
{
transform: "translateY(100px) scale(0)",
opacity: 0,
},
{
transform: "translateY(0px) scale(1)",
opacity: 1,
},
],
{
duration: 300,
}
);
animate.pause();
domMap.set(dom, { animate });
}
// 创建dom元素
function createDom() {
const parentWrap = document.querySelector(".container");
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.className = "item " + "item" + i;
li.innerHTML = "item" + i;
li.style.background = generateRandomColor();
parentWrap.appendChild(li);
}
}
// 添加元素位置观察
function addDomObserver(li) {
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const targetDomDataMap = domMap.get(entry.target);
if (!targetDomDataMap) return;
if (!targetDomDataMap.loaded) {
targetDomDataMap.animate.play();
// 标识加载过了
targetDomDataMap.loaded = true;
// 动画执行完毕,关闭动画和取消监听
targetDomDataMap.animate.onfinish = () => {
targetDomDataMap.animate.cancel();
intersectionObserver.unobserve(entry.target);
};
}
}
});
});
// 开始监听
const allItemList = document.querySelectorAll(".item");
for (let i = 0; i < allItemList.length; i++) {
intersectionObserver.observe(allItemList[i]);
}
}
// 创建动画(dom创建完成之后)
function createAnimate() {
const allItemList = document.querySelectorAll(".item");
for (let i = 0; i < allItemList.length; i++) {
console.log(checkElementShowTopViewPort(allItemList[i]));
if (!checkElementShowTopViewPort(allItemList[i])) {
addAnimate(allItemList[i]);
}
}
}
createDom();
createAnimate();
addDomObserver();
</script>
</body>
</html>