转眼间,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)也可以轻松实现,只不过写法不同,但是核心方法一样。
网友评论文明上网理性发言 已有2人参与
发表评论:
评论列表