1. IntersectionObserver检测实现
2. mock模拟的数据
效果:
html
<div id="container">
<div class="left">
<ul>
<li><a href="#title1">标题1</a></li>
<li><a href="#title2">标题2</a></li>
<li><a href="#title3">标题3</a></li>
<li><a href="#title4">标题4</a></li>
<li><a href="#title5">标题5</a></li>
<li><a href="#title6">标题6</a></li>
<li><a href="#title7">标题7</a></li>
</ul>
</div>
<div class="right">
<p id="title1" class="title">标题1</p>
<p class="content"></p>
<p id="title2" class="title">标题2</p>
<p class="content"></p>
<p id="title3" class="title">标题3</p>
<p class="content"></p>
<p id="title4" class="title">标题4</p>
<p class="content"></p>
<p id="title5" class="title">标题5</p>
<p class="content"></p>
<p id="title6" class="title">标题6</p>
<p class="content"></p>
<p id="title7" class="title">标题7</p>
<p class="content"></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Mock.js/1.0.1-beta3/mock-min.js"></script>
css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
li{
list-style: none;
}
a{
text-decoration: none;
color: black;
}
body{
background-color: #efefef;
}
#container{
width: 100%;
min-width: 1200px;
position: relative;
}
.left{
width: 13%;
height: 100vh;
float: left;
position: sticky;
left: 0;
top: 0;
background-color: cadetblue;
}
.left ul li{
width: 100%;
padding: 20px;
font-size: 19px;
text-align: center;
}
.left ul li .active{
color: goldenrod;
}
.right{
margin-left: 15%;
padding: 20px;
display: none;
}
.right .title{
font-size: 40px;
background-color: aquamarine;
text-align: center;
padding: 20px;
}
.right .content{
font-size: 15px;
line-height: 40px;
text-indent: 30px;
padding: 20px;
}
JS
(() => {
//获取所有的标题
let titles = document.querySelectorAll(".title");
// 获取所有的内容
let content = document.querySelectorAll(".content");
// 获取左侧列表
let hrefs = document.querySelectorAll('a[href^="#"]');
// 获取右侧内容
let right = document.querySelector(".right");
// 标题第一个元素的Y值
let titlesBefore = titles[0].getBoundingClientRect().top;
// 生成随机内容 写入html
window.onload = function () {
let rd = Mock.Random;
content.forEach((item) => {
item.innerHTML = rd.cparagraph(200);
});
right.style.display = "block";
};
// a标签点击事件
hrefs.forEach((item) => {
item.addEventListener("click", function (e) {
setDomActive(e.target);
});
});
// 设置a标签的样式
function setDomActive(dom) {
if (!dom) return;
//清理样式
clearActive();
// dom是不是dom元素
if (dom instanceof HTMLElement) {
dom.classList.add("active");
return;
}
// 如果是id
if (dom.startsWith("#")) {
let domEle = document.querySelector(`a[href="${dom}"]`);
if (domEle) {
domEle.classList.add("active");
}
}
}
// 监视
let observer = new IntersectionObserver(
(entries) => {
let isIntersecting = false;
let titleTopMapArr = new Map();
let titleTopArr = new Array();
for (let i = 0; i < entries.length; i++) {
// 记录每一个标题的位置
let top = Math.abs(entries[i].target.getBoundingClientRect().top);
titleTopMapArr.set(entries[i].target, top);
titleTopArr.push(top);
// 判断是否在可视区域
let item = entries[i];
if (item.isIntersecting) {
isIntersecting = true;
// 清理样式
clearActive();
let id = item.target.id;
setDomActive(`#${id}`);
}
}
// 第一个标题的top
let curTop = titles[0].getBoundingClientRect().top;
// 如果不在可视区域 而且当前位置大于上次位置 证明网页向下滚动
if (!isIntersecting) {
let activeDom = document.querySelector(".active");
if (curTop > titlesBefore) {
if (activeDom) {
clearActive();
activeDom.parentElement.previousElementSibling.firstChild.classList.add(
"active"
);
}
return;
}
// 刷新
if (!activeDom) {
titleTopMapArr.forEach((value, key) => {
if (value === Math.min(...titleTopArr)) {
let id = key.id;
setDomActive(`#${id}`);
}
});
}
}
// 更新位置
titlesBefore = curTop;
},
{
root: null,
rootMargin: "0px 0px -70% 0px",
threshold: 0,
}
);
// 监视标题
titles.forEach((item, index) => {
observer.observe(item);
});
// 清理左侧列表的样式
function clearActive() {
hrefs.forEach((item) => {
item.classList.remove("active");
});
}
})();