基于浏览器窗口
滚动监听↑
离开检测 ↑
进入页面时会检测一遍有没有符合条件的元素 ↑
点击导航滚动跳转↑
基于DOM元素
滚动监听 + 离开检测 ↑
点击导航滚动跳转↑
也有进入页面时检测一遍有没有符合条件的元素的功能,就不上图了
开发过程
最近喜欢造轮子,滚动监听是我造轮子计划中的一部分,因为它太常见了。一开始我研究了 Bootstrap 的滚动监听,以它为原型我写了一个 demo,但是我发现这东西不给力,一是因为它太依赖相对定位属性,一个复杂的网页中有太多相对定位,这样会导致滚动监听失效。二是因为它的跳转只是简单的通过锚进行跳转,这样视觉效果不够震撼。所以我继续研究,努力改进以上两点,最终我还是成功研究出来了。
原理
基于浏览器窗口
利用 getBoundingClientRect() 方法检测每个元素距离浏览器顶部的距离,找出符合距离的那个元素,然后改变那个元素对应的导航。
基于DOM元素
利用 getBoundingClientRect().top 的值加上 scrollTop 的值得到元素距离文档顶部的距离,两个元素的距离文档顶部的距离相减就是这两个元素之间的距离。
点击导航滚动跳转
求出目标到另一个目标的距离,然后滚动条移动这段距离即可。
插件特点
1、原生js编写,ES6面向对象写法,所以不支持IE,要在IE上使用需要转成ES5。
2、既可以基于浏览器窗口监听也可以基于DOM元素监听。
3、不依赖定位属性。
4、可以一次绑定多个导航栏。
5、有离开监听元素检测。
6、页面加载完成时会检测一遍有没有符合条件的监听元素。
7、点击导航栏可以滚动到指定监听元素。
8、性能强大,滚动条一路下(上)拉,每次拉到指定的位置,只会执行一次修改DOM。
9、性能强大,有节流措施。
在线预览(可以复制进html在线运行工具运行)
基于浏览器窗口
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
}
body{
padding-left: 100px;
}
.scrollSpy{
}
.section{
height: 500px;
width: 1000px;
color: white;
font-size: 150px;
text-align: center;
line-height: 500px;
margin: 100px 0;
padding: 100px 0;
border: 10px solid black;
}
.section:nth-child(1){
background-color: red;
}
.section:nth-child(2){
background-color: blue;
}
.section:nth-child(3){
background-color: pink;
}
.section:nth-child(4){
background-color: yellow;
}
.section:nth-child(5){
background-color: gray;
}
.section:nth-child(6){
background-color: green;
}
.section:nth-child(7){
background-color: cyan;
}
.section:nth-child(8){
background-color: orange;
}
.section:nth-child(9){
background-color: blueviolet;
}
.section:nth-child(10){
background-color: aquamarine;
}
.nav-container{
position: fixed;
top:100px;
right: 5%;
border: 1px solid #77AADD;
}
.nav{
list-style: none;
padding-left: 0;
}
.nav>li{
height: 50px;
width: 80px;
text-align: center;
line-height: 50px;
background-color: #999999;
color: #ffffff;
}
.nav>li:hover{
cursor: pointer;
}
.nav .active{
background-color: white;
color: black;
}
</style>
</head>
<body class="scrollSpy" data-target=".nav" data-offset="150" style="height: 2900px;">
<div >
<div style="padding: 500px 0 1600px;margin-top: 500px;position: relative">
<div class="section spy">1</div>
<div class="section spy">2</div>
<div class="section spy">3</div>
<div class="section spy">4</div>
<div class="section spy">5</div>
<div class="section spy">6</div>
<div class="section spy">7</div>
<div class="section spy">8</div>
<div class="section spy">9</div>
<div class="section spy">10</div>
</div>
</div>
<nav class="nav-container">
<ul class="nav">
<li class="nav-item">一</li>
<li class="nav-item">二</li>
<li class="nav-item">三</li>
<li class="nav-item">四</li>
<li class="nav-item">五</li>
<li class="nav-item">六</li>
<li class="nav-item">七</li>
<li class="nav-item">八</li>
<li class="nav-item">九</li>
</ul>
</nav>
<nav class="nav-container" style="right: 15%">
<ul class="nav">
<li class="nav-item">一</li>
<li class="nav-item">二</li>
<li class="nav-item">三</li>
<li class="nav-item">四</li>
<li class="nav-item">五</li>
<li class="nav-item">六</li>
<li class="nav-item">七</li>
<li class="nav-item">八</li>
<li class="nav-item">九</li>
<li class="nav-item">十</li>
<li class="nav-item">十一</li>
</ul>
</nav>
<script>
window.onload = function () {
new ScrollSpy({
delay:10,
target: function (a,target,targetNav,targetSpy) {
target.classList.add("active");
},
order: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
top: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
bottom: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
enter: function (target,targetNav,targetSpy) {
target.classList.add("active");
}
});
};
class ScrollSpy {
constructor(object) {
if( typeof object.target !== "function" || typeof object.order !== "function" || typeof object.top !== "function" || typeof object.enter !== "function"){
return;
}
let scrollSpyArr = document.getElementsByClassName("scrollSpy");
this.delay = typeof object.delay ==="number" ? object.delay : 10;
if (scrollSpyArr.length <= 0) {
return;
}
for (let i = 0, len = scrollSpyArr.length; i < len; i++) {
let scrollSpy = scrollSpyArr[i];
let offset = scrollSpy.getAttribute("data-offset") ? scrollSpy.getAttribute("data-offset") : 0;
let targetName = scrollSpy.getAttribute("data-target");
if (!targetName) {
return;
}
let scrollSpyItemArr = scrollSpy.getElementsByClassName("spy");
if (scrollSpyItemArr.length <= 0) {
return;
}
let navArr = this.getElements(targetName);//不想用 querySelectorAll(),就是喜欢瞎折腾。
if (navArr.length <= 0) {
return;
}
let navChildren;
if (scrollSpy.tagName === "BODY") {
//body 元素
let myBrower = this.myBrowser();
let windowObj;
if (myBrower === "IE" || myBrower === "Firefox") {
windowObj = document.documentElement;
} else {
windowObj = document.body;
}
for (let a = 0, b = navArr.length; a < b; a++) {
let navChildren = navArr[a].getElementsByClassName("nav-item");
let isJump = false;
for (let n = 0, len = scrollSpyItemArr.length; n < len; n++) {
let i = n; //可恶的IE浏览器。
if (navChildren[i]) {
navChildren[i].addEventListener("click", function () {
if (isJump === true) {
return false;
}
isJump = true;
let speed = (offset - scrollSpyItemArr[i].getBoundingClientRect().top) / 50;
let reg = /^((-?)\d+)(\.*\d+)?$/;
let res = reg.exec(speed.toString());
let num = 0;
let id = setInterval(function () {
num++;
windowObj.scrollTop -= parseFloat(res[1]);
if (num === 50) {
clearInterval(id);
if (res[3]) {
if (res[2]) {
windowObj.scrollTop -= -parseFloat(res[3]) * 50 - 1;
} else {
windowObj.scrollTop -= parseFloat(res[3]) * 50 - 1;
}
}
isJump = false;
}
}, 10);
});
}
}
}
let arr1 = [];
let arr2 = [];
let p = 0, t = 0;
let maxIndex = 0;
if(windowObj.scrollTop === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top <= offset){
for (let n = 0, m = navArr.length; n < m; n++) {
object.enter(navArr[n].getElementsByClassName("nav-item")[0],navArr[n],scrollSpy);
}
}
}else{
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
function handle(){
p = windowObj.scrollTop;
if (t <= p) {
arr2 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr1[maxIndex]) {
arr1[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
if(!arr1["top"]){
arr1["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
if(!arr1["bottom"]){
arr1["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
} else {
arr1 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr2[maxIndex]) {
arr2[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr2[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === scrollSpyItemArr.length - 1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr2[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
if(!arr2["top"]){
arr2["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
if(!arr2["bottom"]){
arr2["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
}
t = p;
}
window.addEventListener("scroll", this.throttle(handle,this.delay));
} else {
//普通元素
for (let a = 0, b = navArr.length; a < b; a++) {
let navChildren = navArr[a].getElementsByClassName("nav-item");
let isJump = false;
for (let n = 0, len = scrollSpyItemArr.length; n < len; n++) {
let i = n;
if(navChildren[i]){
navChildren[i].addEventListener("click", function () {
if (isJump === true) {
return false;
}
isJump = true;
let targetScrollTop = scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top - offset;
let speed = (-targetScrollTop) / 50;
let reg = /^((-?)\d+)(\.*\d+)?$/;
let res = reg.exec(speed.toString());
let num = 0;
let id = setInterval(function () {
num++;
scrollSpy.scrollTop -= parseFloat(res[1]);
if (num === 50) {
clearInterval(id);
if (res[3]) {
if (res[2]) {
scrollSpy.scrollTop -= -parseFloat(res[3]) * 50 - 1;
} else {
scrollSpy.scrollTop -= parseFloat(res[3]) * 50 - 1;
}
}
isJump = false;
}
}, 10);
});
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset){
for (let n = 0, m = navArr.length; n < m; n++) {
object.enter(navArr[n].getElementsByClassName("nav-item")[0],navArr[n],scrollSpy);
}
}
let arr1 = [];
let arr2 = [];
let p = 0, t = 0;
let maxIndex = 0;
function handle(){
p = scrollSpy.scrollTop;
if (t <= p) {
arr2 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr1[maxIndex]) {
arr1[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
if (!arr1["top"]) {
arr1["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
if (!arr1["bottom"]) {
arr1["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
} else {
arr1 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr2[maxIndex]) {
arr2[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
arr2[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === scrollSpyItemArr.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
arr2[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
if (!arr2["top"]) {
arr2["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
if (!arr2["bottom"]) {
arr2["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
}
t = p;
}
scrollSpy.addEventListener("scroll",this.throttle(handle,this.delay));
}
}
}
getElements(_name) {
let reg = /^(\.|#)(\S+)$/;
let res = reg.exec(_name);
let el;
if (res) {
if (res[1] === ".") {
reg = /^[0-9]/;
let name = res[2];
res = reg.exec(res[2]);
if (res === null) {
el = document.getElementsByClassName(name);
} else {
throw new Error();
}
} else {
reg = /^[0-9]/;
let name = res[2];
res = reg.exec(res[2]);
if (res === null) {
el = document.getElementById(name);
} else {
throw new Error();
}
}
} else {
reg = /^[A-z]+$/;
res = reg.exec(_name);
if (res) {
el = document.getElementsByTagName(res[0]);
} else {
throw new Error();
}
}
return el;
}
myBrowser() {
let userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
if (userAgent.indexOf("Opera") > -1) {
return "Opera" //判断是否Opera浏览器
}
if (userAgent.indexOf("Firefox") > -1) {
return "Firefox"; //判断是否Firefox浏览器
}
if (userAgent.indexOf("Chrome") > -1) {
return "Chrome";
}
if (userAgent.indexOf("Safari") > -1) {
return "Safari"; //判断是否Safari浏览器
}
if ((userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1) || (userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1)) {
return "IE"; //判断是否IE浏览器
}
if (userAgent.indexOf("Edge") > -1) {
return "Edge";
}
}
throttle (func, delay) {
let prev = Date.now();
return function() {
let context = this;
let args = arguments;
let now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
}
</script>
</body>
</html>
基于DOM元素
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
}
body{
padding-left: 100px;
}
.scrollSpy{
height: 800px;
width: 1200px;
margin: 800px 0 ;
overflow-y: auto;
}
.section{
height: 500px;
width: 1000px;
color: white;
font-size: 150px;
text-align: center;
line-height: 500px;
margin: 100px 0;
padding: 100px 0;
border: 10px solid black;
}
.section:nth-child(1){
background-color: red;
}
.section:nth-child(2){
background-color: blue;
}
.section:nth-child(3){
background-color: pink;
}
.section:nth-child(4){
background-color: yellow;
}
.section:nth-child(5){
background-color: gray;
}
.section:nth-child(6){
background-color: green;
}
.section:nth-child(7){
background-color: cyan;
}
.section:nth-child(8){
background-color: orange;
}
.section:nth-child(9){
background-color: blueviolet;
}
.section:nth-child(10){
background-color: aquamarine;
}
.nav-container{
position: fixed;
top:100px;
right: 5%;
border: 1px solid #77AADD;
}
.nav{
list-style: none;
padding-left: 0;
}
.nav>li{
height: 50px;
width: 80px;
text-align: center;
line-height: 50px;
background-color: #999999;
color: #ffffff;
}
.nav>li:hover{
cursor: pointer;
}
.nav .active{
background-color: white;
color: black;
}
</style>
</head>
<body style="height: 2900px;">
<div class="scrollSpy" data-target=".nav" data-offset="150" >
<div style="padding: 500px 0 1600px;margin-top: 500px;position: relative">
<div class="section spy">1</div>
<div class="section spy">2</div>
<div class="section spy">3</div>
<div class="section spy">4</div>
<div class="section spy">5</div>
<div class="section spy">6</div>
<div class="section spy">7</div>
<div class="section spy">8</div>
<div class="section spy">9</div>
<div class="section spy">10</div>
</div>
</div>
<nav class="nav-container">
<ul class="nav">
<li class="nav-item">一</li>
<li class="nav-item">二</li>
<li class="nav-item">三</li>
<li class="nav-item">四</li>
<li class="nav-item">五</li>
<li class="nav-item">六</li>
<li class="nav-item">七</li>
<li class="nav-item">八</li>
<li class="nav-item">九</li>
</ul>
</nav>
<nav class="nav-container" style="right: 15%">
<ul class="nav">
<li class="nav-item">一</li>
<li class="nav-item">二</li>
<li class="nav-item">三</li>
<li class="nav-item">四</li>
<li class="nav-item">五</li>
<li class="nav-item">六</li>
<li class="nav-item">七</li>
<li class="nav-item">八</li>
<li class="nav-item">九</li>
<li class="nav-item">十</li>
<li class="nav-item">十一</li>
</ul>
</nav>
<script>
window.onload = function () {
new ScrollSpy({
delay:10,
target: function (a,target,targetNav,targetSpy) {
target.classList.add("active");
},
order: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
top: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
bottom: function (a,target,targetNav,targetSpy) {
target.classList.remove("active");
},
enter: function (target,targetNav,targetSpy) {
target.classList.add("active");
}
});
};
class ScrollSpy {
constructor(object) {
if( typeof object.target !== "function" || typeof object.order !== "function" || typeof object.top !== "function" || typeof object.enter !== "function"){
return;
}
let scrollSpyArr = document.getElementsByClassName("scrollSpy");
this.delay = typeof object.delay ==="number" ? object.delay : 10;
if (scrollSpyArr.length <= 0) {
return;
}
for (let i = 0, len = scrollSpyArr.length; i < len; i++) {
let scrollSpy = scrollSpyArr[i];
let offset = scrollSpy.getAttribute("data-offset") ? scrollSpy.getAttribute("data-offset") : 0;
let targetName = scrollSpy.getAttribute("data-target");
if (!targetName) {
return;
}
let scrollSpyItemArr = scrollSpy.getElementsByClassName("spy");
if (scrollSpyItemArr.length <= 0) {
return;
}
let navArr = this.getElements(targetName);//不想用 querySelectorAll(),就是喜欢瞎折腾。
if (navArr.length <= 0) {
return;
}
let navChildren;
if (scrollSpy.tagName === "BODY") {
//body 元素
let myBrower = this.myBrowser();
let windowObj;
if (myBrower === "IE" || myBrower === "Firefox") {
windowObj = document.documentElement;
} else {
windowObj = document.body;
}
for (let a = 0, b = navArr.length; a < b; a++) {
let navChildren = navArr[a].getElementsByClassName("nav-item");
let isJump = false;
for (let n = 0, len = scrollSpyItemArr.length; n < len; n++) {
let i = n; //可恶的IE浏览器。
if (navChildren[i]) {
navChildren[i].addEventListener("click", function () {
if (isJump === true) {
return false;
}
isJump = true;
let speed = (offset - scrollSpyItemArr[i].getBoundingClientRect().top) / 50;
let reg = /^((-?)\d+)(\.*\d+)?$/;
let res = reg.exec(speed.toString());
let num = 0;
let id = setInterval(function () {
num++;
windowObj.scrollTop -= parseFloat(res[1]);
if (num === 50) {
clearInterval(id);
if (res[3]) {
if (res[2]) {
windowObj.scrollTop -= -parseFloat(res[3]) * 50 - 1;
} else {
windowObj.scrollTop -= parseFloat(res[3]) * 50 - 1;
}
}
isJump = false;
}
}, 10);
});
}
}
}
let arr1 = [];
let arr2 = [];
let p = 0, t = 0;
let maxIndex = 0;
if(windowObj.scrollTop === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top <= offset){
for (let n = 0, m = navArr.length; n < m; n++) {
object.enter(navArr[n].getElementsByClassName("nav-item")[0],navArr[n],scrollSpy);
}
}
}else{
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
function handle(){
p = windowObj.scrollTop;
if (t <= p) {
arr2 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr1[maxIndex]) {
arr1[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
if(!arr1["top"]){
arr1["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
if(!arr1["bottom"]){
arr1["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
} else {
arr1 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr2[maxIndex]) {
arr2[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
arr2[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === scrollSpyItemArr.length - 1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
arr2[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top > offset){
if(!arr2["top"]){
arr2["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom < offset){
if(!arr2["bottom"]){
arr2["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
}
t = p;
}
window.addEventListener("scroll", this.throttle(handle,this.delay));
} else {
//普通元素
for (let a = 0, b = navArr.length; a < b; a++) {
let navChildren = navArr[a].getElementsByClassName("nav-item");
let isJump = false;
for (let n = 0, len = scrollSpyItemArr.length; n < len; n++) {
let i = n;
if(navChildren[i]){
navChildren[i].addEventListener("click", function () {
if (isJump === true) {
return false;
}
isJump = true;
let targetScrollTop = scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top - offset;
let speed = (-targetScrollTop) / 50;
let reg = /^((-?)\d+)(\.*\d+)?$/;
let res = reg.exec(speed.toString());
let num = 0;
let id = setInterval(function () {
num++;
scrollSpy.scrollTop -= parseFloat(res[1]);
if (num === 50) {
clearInterval(id);
if (res[3]) {
if (res[2]) {
scrollSpy.scrollTop -= -parseFloat(res[3]) * 50 - 1;
} else {
scrollSpy.scrollTop -= parseFloat(res[3]) * 50 - 1;
}
}
isJump = false;
}
}, 10);
});
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset){
for (let n = 0, m = navArr.length; n < m; n++) {
object.enter(navArr[n].getElementsByClassName("nav-item")[0],navArr[n],scrollSpy);
}
}
let arr1 = [];
let arr2 = [];
let p = 0, t = 0;
let maxIndex = 0;
function handle(){
p = scrollSpy.scrollTop;
if (t <= p) {
arr2 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr1[maxIndex]) {
arr1[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
arr1[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === navChildren.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
arr1[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
if (!arr1["top"]) {
arr1["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
if (!arr1["bottom"]) {
arr1["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
} else {
arr1 = [];
for (let i = 0, len = scrollSpyItemArr.length; i < len; i++) {
if (scrollSpyItemArr[i].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top <= offset) {
maxIndex = i;
}
}
if (!arr2[maxIndex]) {
arr2[maxIndex] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
if (a === maxIndex) {
if(a === 0){
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
arr2[0] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else if(a === scrollSpyItemArr.length-1){
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
arr2[scrollSpyItemArr.length-1] = false;
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
}else{
object.target(a,navChildren[a],navArr[n],scrollSpy);
}
} else {
object.order(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[0].getBoundingClientRect().top - scrollSpy.getBoundingClientRect().top > offset){
if (!arr2["top"]) {
arr2["top"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.top(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
if(scrollSpyItemArr[scrollSpyItemArr.length-1].getBoundingClientRect().bottom - scrollSpy.getBoundingClientRect().top < offset){
if (!arr2["bottom"]) {
arr2["bottom"] = true;
for (let n = 0, m = navArr.length; n < m; n++) {
navChildren = navArr[n].getElementsByClassName("nav-item");
for (let a = 0, b = navChildren.length; a < b; a++) {
object.bottom(a,navChildren[a],navArr[n],scrollSpy);
}
}
}
}
}
t = p;
}
scrollSpy.addEventListener("scroll",this.throttle(handle,this.delay));
}
}
}
getElements(_name) {
let reg = /^(\.|#)(\S+)$/;
let res = reg.exec(_name);
let el;
if (res) {
if (res[1] === ".") {
reg = /^[0-9]/;
let name = res[2];
res = reg.exec(res[2]);
if (res === null) {
el = document.getElementsByClassName(name);
} else {
throw new Error();
}
} else {
reg = /^[0-9]/;
let name = res[2];
res = reg.exec(res[2]);
if (res === null) {
el = document.getElementById(name);
} else {
throw new Error();
}
}
} else {
reg = /^[A-z]+$/;
res = reg.exec(_name);
if (res) {
el = document.getElementsByTagName(res[0]);
} else {
throw new Error();
}
}
return el;
}
myBrowser() {
let userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
if (userAgent.indexOf("Opera") > -1) {
return "Opera" //判断是否Opera浏览器
}
if (userAgent.indexOf("Firefox") > -1) {
return "Firefox"; //判断是否Firefox浏览器
}
if (userAgent.indexOf("Chrome") > -1) {
return "Chrome";
}
if (userAgent.indexOf("Safari") > -1) {
return "Safari"; //判断是否Safari浏览器
}
if ((userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1) || (userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1)) {
return "IE"; //判断是否IE浏览器
}
if (userAgent.indexOf("Edge") > -1) {
return "Edge";
}
}
throttle (func, delay) {
let prev = Date.now();
return function() {
let context = this;
let args = arguments;
let now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
}
</script>
</body>
</html>
我把源码和使用方法都放进下面仓库,如果有修复bug都只会更新在下面仓库里。
https://gitee.com/island_tears/scrollspy