导航
-- 定位与高亮
课程概要
本课程主要讲解导航栏常见的定位与高亮,在日常网页中,我们经常可以发现随着页面的滚动,导航栏的位置有时候会发生变化,同时和他对应位置的项目会有高亮效果,当我们点击导航栏时,页面会定位到目标位置。通过本课程,完成导航的定位与高亮交互,大家将对页面的滚动和定位有更深入的了解。
主要包含以下功能:
- 滚动超过 banner 区域,固定定位在顶部
- 滚动到与导航内容匹配的区域,导航内容高亮
- 点击导航内容,定位到内容所匹配的位置
知识点
本课程涉及到的主要知识点有:
- document.documentElement.scrollTop 浏览器滚动的位置
let scrollTop = document.documentElement.scrollTop;
console.log(scrollTop);
- scroll 滚动事件
window.addEventListener('scroll',function(){
console.log(document.documentElement.scrollTop)
});
- offsetTop 相对父级元素顶部的距离
<div class="list">
<div class="item"></div>
<div class="item" id="item-2"></div>
</div>
let offsetTop = document.getElementById('item-2').offsetTop;
console.log(offsetTop);
实现步骤
- HTML & CSS
- 构建单例对象PAGE
- 导航滚动定位
- 导航滚动高亮
- 导航点击移动
HTML & CSS
- HTML
<div class="section-banner h400">SECTION Banner</div>
<div class="navigator-bar" id="navigator-bar">
<ul>
<li class="navigator-bar-item" data-nav="section-1">SECTION 1</li>
<li class="navigator-bar-item" data-nav="section-2">SECTION 2</li>
<li class="navigator-bar-item" data-nav="section-3">SECTION 3</li>
<li class="navigator-bar-item" data-nav="section-4">SECTION 4</li>
<li class="navigator-bar-item" data-nav="section-5">SECTION 5</li>
</ul>
</div>
<div class="h400" id="section-1">SECTION 1</div>
<div class="h400" id="section-2">SECTION 2</div>
<div class="h400" id="section-3">SECTION 3</div>
<div class="h400" id="section-4">SECTION 4</div>
<div class="h400" id="section-5">SECTION 5</div>
<div class="h800">SECTION Footer</div>
- css
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.h400{
height: 400px;
border: 1px solid #e4e4e4;
color: #666;
padding: 20px;
}
.h800{
height: 800px;
border: 1px solid #e4e4e4;
color: #666;
padding: 20px;
}
.navigator-bar {
height: 60px;
}
.navigator-bar ul{
display: flex;
list-style-type: none;
background: #f5f5f5;
}
.navigator-bar.fixed-top ul{
position: fixed;
top: 0;
left: 0;
width: 100%;
}
.navigator-bar-item{
flex: 1;
line-height: 60px;
text-align: center;
cursor: pointer;
color: #666;
transition: all .2s ease;
}
.navigator-bar-item:hover{
background: #666;
color: #fff;
}
.navigator-bar-item.active{
background: #333;
color: #fff;
}
构建单例对象PAGE
- PAGE
- init、bind
- PAGE.init
- PAGE.data
- navigatorBarIdArr 存储导航定位的容器 id
- navigatorBarActiveId 当前导航高亮 id
- navigatorBarFixed 导航是否定位在顶部
- navigatorBarFixedOffset 导航距离顶部的高度
- navigatorBarHeight 导航自身高度
const PAGE = {
data: {
navigatorBarIdArr: ['section-1','section-2','section-3','section-4','section-5'],
navigatorBarActiveId: '',
navigatorBarFixed: false,
navigatorBarFixedOffset: 460,
navigatorBarHeight: 60,
},
init: function() {
this.bind();
},
bind: function() {
}
}
PAGE.init();
导航滚动定位
- 为 window 绑定 scroll 事件 refreshNavigator
- 创建 PAGE.refreshNavigator 方法,并调用 PAGE.fixedNavigator 方法
- 创建 PAGE.fixedNavigator,用于判断定位导航
- 获取当前浏览器滚动的距离
- 获取导航需要定位的距离
- 比较滚动距离和定位距离,如果滚动距离大于定位距离,就需要定位
- 获取当前浏览器是否定位的状态,如果与上一步比较的结构不一样,就执行下述方法
- 更新 PAGE.data 中 当前浏览器是否定位的状态
- 获取 navigator-bar 导航元素
- 判断是否要需要定位,在导航元素 className 中添加 fixed-top
- 如果否,就设置导航元素 className 为 navigator-bar
const PAGE = {
data: {
navigatorBarIdArr: ['section-1','section-2','section-3','section-4','section-5'],
navigatorBarActiveId: '',
navigatorBarFixed: false,
navigatorBarFixedOffset: 460,
navigatorBarHeight: 60,
},
init: function() {
this.bind();
},
bind: function() {
// 1. 为 window 绑定 scroll 事件 refreshNavigator
window.addEventListener('scroll',this.refreshNavigator);
},
// 2. 创建 PAGE.refreshNavigator 方法,并调用 PAGE.fixedNavigator 方法
refreshNavigator: function() {
PAGE.fixedNavigator();
},
// 3. 创建 PAGE.fixedNavigator,用于判断定位导航
fixedNavigator: function() {
// 4. 获取当前浏览器滚动的距离
let scrollTop = document.documentElement.scrollTop;
// 5. 获取导航需要定位的距离
let navigatorBarTop = (PAGE.data.navigatorBarFixedOffset + PAGE.data.navigatorBarHeight);
// 6. 比较滚动距离和定位距离,如果滚动距离大于定位距离,就需要定位
let navigatorBarFixed = scrollTop >= navigatorBarTop;
// 7. 获取当前浏览器是否定位的状态,如果与上一步比较的结构不一样,就执行下述方法
if( PAGE.data.navigatorBarFixed !== navigatorBarFixed){
// 8. 更新 PAGE.data 中 当前浏览器是否定位的状态
PAGE.data.navigatorBarFixed = navigatorBarFixed;
// 9. 获取 navigator-bar 导航元素
let navigatorBar = document.getElementById('navigator-bar');
// 10. 判断是否要需要定位,在导航元素 className 中添加 fixed-top
if(navigatorBarFixed){
navigatorBar.className = 'navigator-bar fixed-top'
// 11. 如果否,就设置导航元素 className 为 navigator-bar
}else{
navigatorBar.className = 'navigator-bar'
}
}
}
}
PAGE.init();
导航滚动高亮
- 在 PAGE.refreshNavigator 中添加调用 PAGE.heightLightNavigator ,在每次滚动都执行
- 定义 PAGE.heightLightNavigator 方法
- 获取当前浏览器滚动高度
- 筛选浏览器滚动过的,需要定位的导航容器 id
- 获取导航定位的 id 容器距离文档顶部距离
- 判断滚动距离是否大于容器距离文档顶部距离
- 定义当前应该高亮的容器 id ,为滚动过的最后一个,如果没有就为空
- 判断当前应该高亮的容器和 PAGE.data 存储高亮的容器 id 是否一致
- 更新 PAGE.data 存储高亮的容器 id
- 获取所有 navigator-bar-item 导航项目元素
- 循环导航项目元素
- 获取每一个导航元素
- 获取导航元素的 data-nav 中对应容器的 id 值
- 判断元素对应容器的 id 值是否和当前应该高亮的容器的 id 值是否一致,为其 className 添加 active
- 如果不一致,设置回 navigator-bar-item
const PAGE = {
data: {
navigatorBarIdArr: ['section-1','section-2','section-3','section-4','section-5'],
navigatorBarActiveId: '',
navigatorBarFixed: false,
navigatorBarFixedOffset: 460,
navigatorBarHeight: 60,
},
init: function() {
this.bind();
},
bind: function() {
window.addEventListener('scroll',this.refreshNavigator);
},
refreshNavigator: function() {
PAGE.fixedNavigator();
// 1. 在 PAGE.refreshNavigator 中添加调用 PAGE.heightLightNavigator
PAGE.heightLightNavigator();
},
// 2. 定义 PAGE.heightLightNavigator 方法
heightLightNavigator: function() {
// 3. 获取当前浏览器滚动高度
let scrollTop = document.documentElement.scrollTop;
// 4. 筛选浏览器滚动过的,需要定位的导航容器 id
let filterNav = PAGE.data.navigatorBarIdArr.filter( data => {
// 5. 获取导航定位的 id 容器距离文档顶部距离
let offsetTop = document.getElementById(data).offsetTop;
// 6. 判断滚动距离是否大于容器距离文档顶部距离
return scrollTop >= offsetTop - PAGE.data.navigatorBarHeight
})
// 7. 定义当前应该高亮的容器 id ,为滚动过的最后一个,如果没有就为空
let navigatorBarActiveId = filterNav.length ? filterNav[filterNav.length - 1] : '';
// 8. 判断当前应该高亮的容器和 PAGE.data 存储高亮的容器 id 是否一致
if(PAGE.data.navigatorBarActiveId !== navigatorBarActiveId){
// 9. 更新 PAGE.data 存储高亮的容器 id
PAGE.data.navigatorBarActiveId = navigatorBarActiveId;
// 10. 获取所有 navigator-bar-item 导航项目元素
let navigatorBarItems = document.getElementsByClassName('navigator-bar-item');
// 11. 循环导航项目元素
for (let i = 0; i < navigatorBarItems.length; i++) {
// 12. 获取每一个导航元素
let navigatorBarItem = navigatorBarItems[i];
// 13. 获取导航元素的 data-nav 中对应容器的 id 值
let dataNav = navigatorBarItem.dataset.nav;
// 14. 判断元素对应容器的 id 值是否和当前应该高亮的容器的 id 值是否一致,为其 className 添加 active
if(dataNav === navigatorBarActiveId){
navigatorBarItem.className = 'navigator-bar-item active';
// 15. 如果不一致,设置回 navigator-bar-item
}else{
navigatorBarItem.className = 'navigator-bar-item';
}
}
}
},
fixedNavigator: function() {
let scrollTop = document.documentElement.scrollTop;
let navigatorBarTop = (PAGE.data.navigatorBarFixedOffset + PAGE.data.navigatorBarHeight);
let navigatorBarFixed = scrollTop >= navigatorBarTop;
if( PAGE.data.navigatorBarFixed !== navigatorBarFixed){
PAGE.data.navigatorBarFixed = navigatorBarFixed;
let navigatorBar = document.getElementById('navigator-bar');
if(navigatorBarFixed){
navigatorBar.className = 'navigator-bar fixed-top'
}else{
navigatorBar.className = 'navigator-bar'
}
}
}
}
PAGE.init();
导航点击移动
- 添加委托绑定方法 onEventLister
- 在 bind 中委托绑定点击 navigator-bar-item 触发 goNavigator 事件
- 定义 PAGE.goNavigator 点击导航页面定位事件
- 获取点击元素中对应容器 id
- 定义该容器 id 于顶部的距离值
- 设置文档位置到距离中
const PAGE = {
...,
bind: function() {
window.addEventListener('scroll',this.refreshNavigator);
// 2. 在 bind 中委托绑定点击 navigator-bar-item 触发 goNavigator 事件
this.onEventLister(navigatorBar,'click','navigator-bar-item',this.goNavigator);
},
// 1. 添加委托绑定方法 onEventLister
onEventLister: function(parentNode,action,childClassName,callback) {
parentNode.addEventListener(action,function(e){
e.target.className.indexOf(childClassName) >= 0 && callback(e);
})
},
...,
// 3. 定义 PAGE.goNavigator 点击导航页面定位事件
goNavigator: function(e) {
// 4. 获取点击元素中对应容器 id
let id = e.target.dataset.nav;
// 5. 定义该容器 id 于顶部的距离值
let offsetTop = document.getElementById(id).offsetTop - PAGE.data.navigatorBarHeight;
// 6. 设置文档位置到距离中
document.documentElement.scrollTop = offsetTop;
}
}
PAGE.init();
代码示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NavigatorBar</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.h400{
height: 400px;
border: 1px solid #e4e4e4;
color: #666;
padding: 20px;
}
.h800{
height: 800px;
border: 1px solid #e4e4e4;
color: #666;
padding: 20px;
}
.navigator-bar {
height: 60px;
}
.navigator-bar ul{
display: flex;
list-style-type: none;
background: #f5f5f5;
}
.navigator-bar.fixed-top ul{
position: fixed;
top: 0;
left: 0;
width: 100%;
}
.navigator-bar-item{
flex: 1;
line-height: 60px;
text-align: center;
cursor: pointer;
color: #666;
transition: all .2s ease;
}
.navigator-bar-item:hover{
background: #666;
color: #fff;
}
.navigator-bar-item.active{
background: #333;
color: #fff;
}
</style>
</head>
<body>
<div class="section-banner h400">SECTION Banner</div>
<div class="navigator-bar" id="navigator-bar">
<ul>
<li class="navigator-bar-item" data-nav="section-1">SECTION 1</li>
<li class="navigator-bar-item" data-nav="section-2">SECTION 2</li>
<li class="navigator-bar-item" data-nav="section-3">SECTION 3</li>
<li class="navigator-bar-item" data-nav="section-4">SECTION 4</li>
<li class="navigator-bar-item" data-nav="section-5">SECTION 5</li>
</ul>
</div>
<div class="h400" id="section-1">SECTION 1</div>
<div class="h400" id="section-2">SECTION 2</div>
<div class="h400" id="section-3">SECTION 3</div>
<div class="h400" id="section-4">SECTION 4</div>
<div class="h400" id="section-5">SECTION 5</div>
<div class="h800">SECTION Footer</div>
<script type="text/javascript">
const PAGE = {
data: {
navigatorBarIdArr: ['section-1','section-2','section-3','section-4','section-5'],
navigatorBarActiveId: '',
navigatorBarFixed: false,
navigatorBarFixedOffset: 460,
navigatorBarHeight: 60,
},
init: function() {
this.bind();
},
bind: function() {
window.addEventListener('scroll',this.refreshNavigator);
let navigatorBar = document.getElementById('navigator-bar');
this.onEventLister(navigatorBar,'click','navigator-bar-item',this.goNavigator);
},
onEventLister: function(parentNode,action,childClassName,callback) {
parentNode.addEventListener(action,function(e){
e.target.className.indexOf(childClassName) >= 0 && callback(e);
})
},
refreshNavigator: function(e) {
PAGE.fixedNavigator();
PAGE.heightLightNavigator();
},
fixedNavigator: function() {
let scrollTop = document.documentElement.scrollTop;
let navigatorBarTop = (PAGE.data.navigatorBarFixedOffset + PAGE.data.navigatorBarHeight);
let navigatorBarFixed = scrollTop >= navigatorBarTop;
if( PAGE.data.navigatorBarFixed !== navigatorBarFixed){
PAGE.data.navigatorBarFixed = navigatorBarFixed;
let navigatorBar = document.getElementById('navigator-bar');
if(navigatorBarFixed){
navigatorBar.className = 'navigator-bar fixed-top'
}else{
navigatorBar.className = 'navigator-bar'
}
}
},
heightLightNavigator: function() {
let scrollTop = document.documentElement.scrollTop;
let filterNav = PAGE.data.navigatorBarIdArr.filter( data => {
let offsetTop = document.getElementById(data).offsetTop;
return scrollTop >= offsetTop - PAGE.data.navigatorBarHeight
})
let navigatorBarActiveId = filterNav.length ? filterNav[filterNav.length - 1] : '';
if(PAGE.data.navigatorBarActiveId !== navigatorBarActiveId){
PAGE.data.navigatorBarActiveId = navigatorBarActiveId;
let navigatorBarItems = document.getElementsByClassName('navigator-bar-item');
for (let i = 0; i < navigatorBarItems.length; i++) {
let navigatorBarItem = navigatorBarItems[i];
let dataNav = navigatorBarItem.dataset.nav;
if(dataNav === navigatorBarActiveId){
navigatorBarItem.className = 'navigator-bar-item active';
}else{
navigatorBarItem.className = 'navigator-bar-item';
}
}
}
},
goNavigator: function(e) {
let id = e.target.dataset.nav;
let offsetTop = document.getElementById(id).offsetTop - PAGE.data.navigatorBarHeight;
document.documentElement.scrollTop = offsetTop;
}
}
PAGE.init();
</script>
</body>
</html>