转眼间,2022快过一半啦,今天迎来了6月的第一天,也是儿童节,也祝福天下的大小朋友们节日快乐!
今天小编继续给大家分享点东西,关于我们页面中导航的一些定位已经自动切换功能。废话不多说,往下看吧。
预览
先来看张图:
这是我们刚打开页面进来的默认效果,当我们往下拉到导航的时候,会变化成如下图显示效果:
导航直接吸顶了,并且固定,我们继续往下滑动后,会根据导航对应的内容,导航的标签进行状态切换。是不是很好玩?接下来看下代码如何实现吧!
首先需要布局下页面,直接放代码。
HTML
<div class="right-section">
<div class="top-section">Web前端之家<br />www.jiangweishan.com</div>
<div class="nav-bar-wrap">
<ul class="nav-bar" onclick="selectNav()">
<li data-content="content1" class="active">Web前端之家</li>
<li data-content="content2" class="">Web前端前端教程</li>
<li data-content="content3" class="">React教程</li>
</ul>
</div>
<div class="nav-content-container">
<div class="content content1">欢迎来到Web前端之家,我们的网址是https://www.jiangweishan.com</div>
<div class="content content2">Web前端前端教程</div>
<div class="content content3">React教程</div>
</div>
</div>
CSS
html {
height: 100%;
overflow: hidden;
}
body {
margin: 0;
padding-top: 0;
height: 100%;
font-size: 0;
overflow: auto;
background-color: #f5f6fa;
}
.right-section {
position: relative; /* 这是重点 */
display: inline-block;
margin-left: 24px;
width: 100%;
font-size: 14px;
vertical-align: top;
}
.top-section {
background: #fff;
padding: 120px 20px;
text-align: center;
line-height: 1.6;
font-size: 26px;
font-weight: bold;
}
.nav-bar-wrap {
margin: 16px 0;
height: 55px;
}
.nav-bar {
margin: 0;
padding: 0;
height: 100%;
border-radius: 5px;
background: #fff;
box-shadow: 0 1px 8px 4px rgba(0,0,0,.02);
}
.nav-bar li {
display: inline-block;
padding: 16px;
cursor: pointer;
}
.nav-bar li.active {
color: #387af6;
border-bottom: 2px solid #387af6;
}
.content{
padding: 20px;
}
.content1 {
height: 300px;
background: #fff;
}
.content2 {
margin: 16px 0;
height: 500px;
background: #fff;
}
.content3 {
height: 1000px;
background: #fff;
}
Javascript
Javascript代码是核心,当然了,如果您对于基础JS代码熟悉的话,对于下面的JS代码就不会感到棘手。
<script>
var navBar = document.querySelector('.nav-bar');
var menu = document.querySelectorAll('.nav-bar li');
var scrollContainer = document.querySelector('body');
var needFixed = true;
// 这是body的paddingTop,因为导航栏的offsetParent不是body,因为要减去body的paddingTop才能用来跟导航栏的offsetTop做比较
var extraFixed = -24;
// 55 - 24:导航栏高度 - body的paddingTop
// 在不吸顶的情况下,导航指定的内容只要滚动到body顶部就算到了该内容了的导航了,即滚动了【内容的offsetTop + body的paddingTop】的距离
// 但是吸顶之后,只要滚动到吸顶导航栏底部就算到了指定导航内容了,所以相当于只要滚动【内容的offsetTop + body的paddingTop - 吸顶导航栏的高度】的距离就会到达临界值
// 转换成公式来理解,c代表导航内容的offsetTop,s代表滚动的距离,body的paddingTop为24,吸顶导航栏高度为55。只要滚动距离大于等于上面说的临界值,即肯定到达了对应导航。
// 因此公式为: s >= c + 24 - 55, 即 s + 31 >= c 时,到达条件成立,因此滚动容器的scrollTop都要加上31,才是拿来判断的值
var extraScroll = 31;
var offsetTops = {};
var isSupportSticky = validateSticky();
calcTop(true);
scrollContainer.addEventListener('scroll', handleScroll);
window.addEventListener('resize', hanldeResize);
/**
* 计算页面的各个offsetTop
*/
function calcTop(recalNav) {
recalNav && (offsetTops.navBar = navBar.offsetTop);
var contents = ['content1', 'content2', 'content3'];
for (var j = 0; j < contents.length; j++) {
offsetTops[contents[j]] = document.querySelector('.' + contents[j]).offsetTop;
}
}
/**
* 选择标题跳到对应内容
*/
function selectNav(e) {
var ev = e || event;
var target = ev.target || ev.srcElement; // 兼容IE
this.resetNavSelect();
target.className = 'active';
scrollContainer.scrollTop = offsetTops[target.getAttribute('data-content')] - extraScroll;
}
/**
* 重置导航栏激活样式
*/
function resetNavSelect() {
for (var i = 0; i < menu.length; i++) {
menu[i].className = '';
}
}
/**
* 检查浏览器是否有支持的sticky值,没有返回false,有就添加sticky相关css,实现吸顶
*/
function validateSticky () {
var supportStickyValue = valiateCssValue('position', 'sticky');
if (supportStickyValue) {
var navBarWrap = document.querySelector('.nav-bar-wrap');
navBarWrap.style.position = supportStickyValue;
navBarWrap.style.top = '0';
return true;
}
return false;
}
/**
* 监听滚动事件,实现吸顶和滚动导航
*/
function handleScroll() {
var top = scrollContainer.scrollTop;
var fixedBaseTop = top + extraScroll; // 这是吸顶之后用来做衡量的scrollTop
var menuLength = menu.length;
if (needFixed && !isSupportSticky) {
// 这是控制导航栏吸顶 - 吸顶
if ((top + extraFixed) >= offsetTops.navBar) {
navBar.style.position = 'fixed';
navBar.style.top = 0;
navBar.style.left = '124px';
navBar.style.width = '300px';
navBar.style.height = '55px';
}
// 这是控制导航栏吸顶 - 取消吸顶
if ((top + extraFixed) < offsetTops.navBar) {
navBar.style.position = 'static';
navBar.style.width = '100%';
navBar.style.height = '100%';
}
}
resetNavSelect();
// 滚动条到达底部就选中最后一个导航
if (top + scrollContainer.clientHeight >= scrollContainer.scrollHeight) {
menu[menuLength - 1].className = 'active';
return;
}
// 以下都为依据滚动自动选择对应导航
for (var i = 0; i < menuLength - 1; i++) {
if (fixedBaseTop >= offsetTops['content' + (i + 1)] && fixedBaseTop < offsetTops['content' + (i + 2)]) {
menu[i].className = 'active';
return;
}
}
if (fixedBaseTop >= offsetTops['content' + (menuLength - 1)]) {
menu[menuLength - 1].className = 'active';
return;
}
menu[0].className = 'active';
}
/**
* 监听resize事件,变化时重新计算offsetTop
*/
function hanldeResize() {
calcTop(true);
}
/**
* 检查浏览器是否支持某个css属性值
*/
function valiateCssValue (key, value) {
var prefix = ['-o-', '-ms-', '-moz-', '-webkit-', ''];
var prefixValue = [];
for (var i = 0; i < prefix.length; i++) {
prefixValue.push(prefix[i] + value)
}
var element = document.createElement('div');
var eleStyle = element.style;
for (var j = 0; j < prefixValue.length; j++) {
eleStyle[key] = prefixValue[j];
}
return eleStyle[key];
}
</script>
上面的代码,是进行封装过的,所以算比较规范了。大家可以拿去直接用。
完整DEMO:
<html><head>
<meta charset="utf-8">
<title>滚动导航并且吸顶功能 | Web前端之家www.jiangweishan.com</title>
<style id="jsbin-css">
html {
height: 100%;
overflow: hidden;
}
body {
margin: 0;
padding-top: 0; /* 这是重点 */
height: 100%;
font-size: 0;
overflow: auto;
background-color: #f5f6fa;
}
.right-section {
position: relative; /* 这是重点 */
display: inline-block;
margin-left: 24px;
width: 100%;
font-size: 14px;
vertical-align: top;
}
.top-section {
background: #fff;
padding: 120px 20px;
text-align: center;
line-height: 1.6;
font-size: 26px;
font-weight: bold;
}
.nav-bar-wrap {
margin: 16px 0;
height: 55px;
}
.nav-bar {
margin: 0;
padding: 0;
height: 100%;
border-radius: 5px;
background: #fff;
box-shadow: 0 1px 8px 4px rgba(0,0,0,.02);
}
.nav-bar li {
display: inline-block;
padding: 16px;
cursor: pointer;
}
.nav-bar li.active {
color: #387af6;
border-bottom: 2px solid #387af6;
}
.content{
padding: 20px;
}
.content1 {
height: 300px;
background: #fff;
}
.content2 {
margin: 16px 0;
height: 500px;
background: #fff;
}
.content3 {
height: 1000px;
background: #fff;
}
</style>
<body>
<div class="right-section">
<div class="top-section">Web前端之家<br />www.jiangweishan.com</div>
<div class="nav-bar-wrap">
<ul class="nav-bar" onclick="selectNav()">
<li data-content="content1" class="active">Web前端之家</li>
<li data-content="content2" class="">Web前端前端教程</li>
<li data-content="content3" class="">React教程</li>
</ul>
</div>
<div class="nav-content-container">
<div class="content content1">欢迎来到Web前端之家,我们的网址是https://www.jiangweishan.com</div>
<div class="content content2">Web前端前端教程</div>
<div class="content content3">React教程</div>
</div>
</div>
<script>
var navBar = document.querySelector('.nav-bar');
var menu = document.querySelectorAll('.nav-bar li');
var scrollContainer = document.querySelector('body');
var needFixed = true;
// 这是body的paddingTop,因为导航栏的offsetParent不是body,因为要减去body的paddingTop才能用来跟导航栏的offsetTop做比较
var extraFixed = -24;
// 55 - 24:导航栏高度 - body的paddingTop
// 在不吸顶的情况下,导航指定的内容只要滚动到body顶部就算到了该内容了的导航了,即滚动了【内容的offsetTop + body的paddingTop】的距离
// 但是吸顶之后,只要滚动到吸顶导航栏底部就算到了指定导航内容了,所以相当于只要滚动【内容的offsetTop + body的paddingTop - 吸顶导航栏的高度】的距离就会到达临界值
// 转换成公式来理解,c代表导航内容的offsetTop,s代表滚动的距离,body的paddingTop为24,吸顶导航栏高度为55。只要滚动距离大于等于上面说的临界值,即肯定到达了对应导航。
// 因此公式为: s >= c + 24 - 55, 即 s + 31 >= c 时,到达条件成立,因此滚动容器的scrollTop都要加上31,才是拿来判断的值
var extraScroll = 31;
var offsetTops = {};
var isSupportSticky = validateSticky();
calcTop(true);
scrollContainer.addEventListener('scroll', handleScroll);
window.addEventListener('resize', hanldeResize);
/**
* 计算页面的各个offsetTop
*/
function calcTop(recalNav) {
recalNav && (offsetTops.navBar = navBar.offsetTop);
var contents = ['content1', 'content2', 'content3'];
for (var j = 0; j < contents.length; j++) {
offsetTops[contents[j]] = document.querySelector('.' + contents[j]).offsetTop;
}
}
/**
* 选择标题跳到对应内容
*/
function selectNav(e) {
var ev = e || event;
var target = ev.target || ev.srcElement; // 兼容IE
this.resetNavSelect();
target.className = 'active';
scrollContainer.scrollTop = offsetTops[target.getAttribute('data-content')] - extraScroll;
}
/**
* 重置导航栏激活样式
*/
function resetNavSelect() {
for (var i = 0; i < menu.length; i++) {
menu[i].className = '';
}
}
/**
* 检查浏览器是否有支持的sticky值,没有返回false,有就添加sticky相关css,实现吸顶
*/
function validateSticky () {
var supportStickyValue = valiateCssValue('position', 'sticky');
if (supportStickyValue) {
var navBarWrap = document.querySelector('.nav-bar-wrap');
navBarWrap.style.position = supportStickyValue;
navBarWrap.style.top = '0';
return true;
}
return false;
}
/**
* 监听滚动事件,实现吸顶和滚动导航
*/
function handleScroll() {
var top = scrollContainer.scrollTop;
var fixedBaseTop = top + extraScroll; // 这是吸顶之后用来做衡量的scrollTop
var menuLength = menu.length;
if (needFixed && !isSupportSticky) {
// 这是控制导航栏吸顶 - 吸顶
if ((top + extraFixed) >= offsetTops.navBar) {
navBar.style.position = 'fixed';
navBar.style.top = 0;
navBar.style.left = '124px';
navBar.style.width = '300px';
navBar.style.height = '55px';
}
// 这是控制导航栏吸顶 - 取消吸顶
if ((top + extraFixed) < offsetTops.navBar) {
navBar.style.position = 'static';
navBar.style.width = '100%';
navBar.style.height = '100%';
}
}
resetNavSelect();
// 滚动条到达底部就选中最后一个导航
if (top + scrollContainer.clientHeight >= scrollContainer.scrollHeight) {
menu[menuLength - 1].className = 'active';
return;
}
// 以下都为依据滚动自动选择对应导航
for (var i = 0; i < menuLength - 1; i++) {
if (fixedBaseTop >= offsetTops['content' + (i + 1)] && fixedBaseTop < offsetTops['content' + (i + 2)]) {
menu[i].className = 'active';
return;
}
}
if (fixedBaseTop >= offsetTops['content' + (menuLength - 1)]) {
menu[menuLength - 1].className = 'active';
return;
}
menu[0].className = 'active';
}
/**
* 监听resize事件,变化时重新计算offsetTop
*/
function hanldeResize() {
calcTop(true);
}
/**
* 检查浏览器是否支持某个css属性值
*/
function valiateCssValue (key, value) {
var prefix = ['-o-', '-ms-', '-moz-', '-webkit-', ''];
var prefixValue = [];
for (var i = 0; i < prefix.length; i++) {
prefixValue.push(prefix[i] + value)
}
var element = document.createElement('div');
var eleStyle = element.style;
for (var j = 0; j < prefixValue.length; j++) {
eleStyle[key] = prefixValue[j];
}
return eleStyle[key];
}
</script>
</body>
</html>
说明:
因为这里只是一个简单的DEMO,如果大家在应用的时候出现问题,也可以对应的去调整下。
总结
关于这种导航功能的应用,还有很多方法,比如用插件等。我们用框架(vue,react)也可以轻松实现,只不过写法不同,但是核心方法一样。