一、需求分解
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> <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> <nav> <!-- <a >导航1</a> <a >导航2</a> <a >导航3</a> <a >导航4</a> <a >导航5</a> <a >导航6</a> <a >导航7</a> --> </nav> <div> <!-- <div id="section1">section1</div> <div id="section2">section2</div> <div id="section3">section3</div> <div id="section4">section4</div> <div id="section5">section5</div> <div id="section6">section6</div> <div 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 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>