一、需求分解
1.滚动左侧内容,关联激活右侧导航节点
2.单击右侧导航节点,右侧相应的段落滚动到可视区顶部
二、关键技术点提前知
技术点1:Element.scrollIntoView()
scrollIntoView()方法会滚动元素的父容器,使被调用scrollIntoView()的元素对用户可见。
(1).基本用法介绍:
var el = document.getElementById("p1");
// true 可以省略效果相同
el.scrollIntoView(true)
// alignToTop:Boolean型参数。
// 如果为 true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。
// 如果为 false,元素的底端将和其所在滚动区的可视区域的底端对齐。
el.scrollIntoView(false);
// 可选,scrollIntoViewOptions:{behavior: "smooth", block: "end", inline: "nearest"}
// 可选,behavior :定义动画过渡效果,"auto"或 "smooth" 之一。默认为 "auto"。
// 可选,block:定义垂直方向的对齐,"start", "center", "end", 或 "nearest"之一。默认为 "start"。
// 可选,inline:定义水平方向的对齐,"start", "center", "end", 或 "nearest"之一。默认为 "nearest"。
el.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
el.scrollIntoView({block: "end"});
(2).注意事项
普通的布局没问题,但是要注意,取决于其它元素的布局情况,此元素可能不会完全滚动到顶端或底端。比如整体上已经到顶部了,无法再滚动,那么该元素就不会移动到可视区的顶部。
技术点2:Element.getBoundingClientRect()
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,就是该元素的 CSS 边框大小。返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
三、实现
html:
<!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>
<body>
<div class="container">
<nav>
<!-- <a class="current">导航1</a>
<a >导航2</a>
<a >导航3</a>
<a >导航4</a>
<a >导航5</a>
<a >导航6</a>
<a >导航7</a> -->
</nav>
<div class="wrapper">
<!-- <div class="section" id="section1">section1</div>
<div class="section" id="section2">section2</div>
<div class="section" id="section3">section3</div>
<div class="section" id="section4">section4</div>
<div class="section" id="section5">section5</div>
<div class="section" id="section6">section6</div>
<div class="section" id="section7">section7</div> -->
</div>
</div>
</body>
<script src="./js/jquery.min.js"></script>
</html>
css:
<style>
*{
padding: 0px;
margin: 0px;
}
.container{
width: 100%;
display: flex;
background-color: rgb(255, 216, 222);
}
.container .wrapper{
margin-left: 300px;
}
.container .wrapper .section{
width:1560px;
height: 500px;
text-align: center;
line-height: 500px;
color: #fff;
font-size: 28px;
background: orange;
margin-top: 20px;
}
.container .wrapper .section:nth-child(2n){
background: yellowgreen;
}
.container .wrapper .section:nth-child(3n){
background: plum;
}
.container nav{
width: 300px;
position: fixed;
text-align: center;
}
.container nav div{
display: block;
color: rgb(143, 78, 143);
font-size: 24px;
text-decoration: none;
margin-top: 20px;
}
.container nav .current{
color: orange;
}
.container nav div:not(:first-child){
margin-top: 50px;
}
</style>
JavaScript:
<script>
let list =['section1','section2','section3','section4','section5']
let activeNav=[]
let activeNavIndex=0
// 从内容数据list中,获取段落标题作为导航标题。并为导航节点增加href,以段落的id值作为href的值
list.map((item,index)=>{
activeNav.push({title: item, act: false, href: "#section" + (index+1)});
})
console.log(activeNav);
//渲染导航
function renderNav(){
let dom=$('nav')
let str=``
activeNav.forEach((item,index)=>{
str+=
`
<div class="${activeNavIndex==index? 'current':'' }" onclick='navToPosition(${JSON.stringify(item)},${JSON.stringify(index)})'>${item.title}</div>
`
})
dom.html(str)
}
renderNav()
//渲染左侧滚动区域
function renderContext(){
let dom=$('.wrapper')
let str=``
list.forEach((item,index)=>{
str+=
`
<div class="section" id="${'section'+(Number(index)+1)}">${item}</div>
`
})
dom.html(str)
}
renderContext()
let timeout=null
// 监听滚动条
window.addEventListener("scroll", function (e) {
// 防抖动处理
clearTimeout(timeout)
timeout = setTimeout(() => {
activeNavNode(e)
}, 10)
});
//点击导航右侧滚动区域定位
function navToPosition(item,index){
console.log(item,index);
// 根据导航节点的href信息即id信息,获取对应的元素节点,通过 scrollIntoView 滚动该元素到可视区顶部
document.querySelector(item.href)
console.log(document.querySelector(item.href));
document.querySelector(item.href).scrollIntoView(true)
}
$('nav div').on('click',function(){
$(this).addClass('current')
$(this).siblings().removeClass('current')
})
//通过上面的监听滚动,判断每个div距离视口顶部激活左侧对应的导航条
function activeNavNode(e) {
const nodes = document.getElementsByClassName('section')
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
// 获取该元素此时相对于视口的顶部的距离,即是元素顶部距离视口屏幕上边的距离
let nodeY = node.getBoundingClientRect().y
// 当元素距离视口顶部的距离在 [0,200] 之间,设置激活该元素对应左侧的导航标题,这个数字可以按需定义
// 这里关联内容和导航标签,是巧妙利用了内容在元素集合中的索引序号和导航标签中的一致
// 即是 list 和 activities 和 nodes 中下标相等的元素,具有对应关联的关系
if (nodeY <= 200 && nodeY >= 0) {
// console.log($('nav> div')[i]);
// console.log(document.querySelectorAll('nav > div')[i]);
//document.querySelectorAll('nav > div')[i].setAttribute('class','current')
$('nav> div').each(function(index,el){
if(index==i){
console.log(index,el);
$(el).addClass('current')
$(el).siblings().removeClass('current')
}
})
return
}
}
}
</script>