本文的内容是 JS 滚动监听导航栏更新背景色标识

之前在 bootstrap 文档上看到这样一个效果,如下

因为 bootstrap 是引用了 jQuery 库的,非常容易上手,也能方便地用在各个项目中,但不折腾的前端,跟咸鱼有什么区别。于是就想着不用这些库,自己用 JS 实现以下这个效果,所以就折腾了下。

滚动监听原理

首先了解一下滚动监听的原理,当鼠标滚轮滚动或页面滚动条滑动时,每到一块内容区域,其上方对应的导航栏状态也将更新,那就需要知道每块内容区域相对于顶部的距离,将这个距离跟滚动条滑行的距离做比较,根据比较的结果来决定是否更新导航栏状态标。所以整个大致思路就是:监听页面的 scroll 事件,然后获取当前页面滚动条纵坐标的位置,假设为 posNav;在获取内容区域,这里假设分为内容1,内容2,内容3,获取这些内容块相对于视口顶部的距离,假设是 height ,随着滚动条的滑动,如果 posNav > height1,那么第一个导航标签更新;如果 posNav > height2 ,那么第二个导航更新,…,依次遍历直至底部,在这个过程中,还要注意当其中一个加上了背景色之后,跟它同级的所有元素都要移除掉这个背景色。

瞄点定位

除了根据内容区域滚动更新之外,这里还涉及到一个定位的问题,即点击导航栏,就定位到这个导航所对应的内容区域,这也是很多单页面常有的做法。实现这个最简单、粗暴的方法就是使用瞄点定位,给每个导航 a 标签的 href 属性写上 #id 的格式,然后给对应的内容区域加上 id名称 ,由于 id 是唯一标识,因此点击导航就可直接定位,简单,无兼容问题,非常好的一个实现技巧。

实现

在开始写 js 之前,先准备一个简单的静态页面,默认给第一个导航加上 active 属性,也就是有背景色的效果。

有了页面后,先获取所有导航 li 标签,存在数组里,等会用它更新导航栏。

1
var lis = [...document.querySelectorAll('.nav > ul > li')];

是一个展开运算符,querySelectorAll 选取所有 class 为 nav 下面的 ul 的 li 标签元素的集合,展开运算符将它们都存在数组 lis 里。接着获取内容块区域相对于页面顶部的距离,同样将它们存在数组里。

1
var divs = [...document.querySelectorAll('.mainPage')];

获取到后,用一个 for 循环遍历获取每个 div 的距离顶部的值,使用 getBoundingClientRect() 方法,它返回元素的大小及其相对于视口的位置,我们主要使用 top 属性获取顶部距离。

1
2
3
4
var divHeights = [];
for (var i = 0; i < divs.length; i++) { // 循环每个 div 的距离 top 的值存放至数组
divHeights.push(divs[i].getBoundingClientRect().top);
}

现在所有该有的元素及其数值都有了,可以开始写监听事件了。这里吐个槽,别看上面寥寥几句话就完成了这些属性及其元素的获取,真正在做的时候,我花了不少世间查询这些方法及属性,像 getBoundingClientRect() 、document.doucmentElement.scrollTop 等属性的用法,主要是一直使用现成的框架,导致太依赖框架的那些简便封装好的方法而忽视了原来方法,所以建议各位用框架久了记得回过来看看每个方法对应的作用以及在原生 js 的实现。
好了,监听页面滚动事件把它写在一个函数里,定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function updateNav() {
var scop = document.documentElement.scrollTop;
var k;
for (var i = 0; i < divHeights.length; i++) {
if (scop > divHeights[i] - 50) {
k = i;
}
}
lis[k].classList.add('current');
for (var i = 0; i < lis.length; i++) {
if (i == k) continue;
else lis[i].classList.remove('current');
}
}

最后一句代码调用函数即可

1
window.addEventListener('scroll', updateNav);

至此,所以代码都完成了,来看看最终浏览器的效果吧:

感谢阅读,想要获取完整源代码,请点击 这里

(完)