×

分享一个原生Javascript应用:页面滚动导航且导航吸顶切换功能

作者:Terry2022.06.01来源:Web前端之家浏览:6901评论:2
关键词:jshtml

500.jpg

转眼间,2022快过一半啦,今天迎来了6月的第一天,也是儿童节,也祝福天下的大小朋友们节日快乐!

今天小编继续给大家分享点东西,关于我们页面中导航的一些定位已经自动切换功能。废话不多说,往下看吧。

预览

先来看张图:

image.png

这是我们刚打开页面进来的默认效果,当我们往下拉到导航的时候,会变化成如下图显示效果:

image.png

导航直接吸顶了,并且固定,我们继续往下滑动后,会根据导航对应的内容,导航的标签进行状态切换。是不是很好玩?接下来看下代码如何实现吧!

首先需要布局下页面,直接放代码。

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,如果大家在应用的时候出现问题,也可以对应的去调整下。

总结

关于这种导航功能的应用,还有很多方法,比如用插件等。我们用框架(vuereact)也可以轻松实现,只不过写法不同,但是核心方法一样。

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://www.jiangweishan.com/article/jshtml20220601.html

网友评论文明上网理性发言 已有2人参与

发表评论:

评论列表