造轮子之 高性能滚动监听 + 导航高亮 + 点击导航滚动跳转 + sroll节流

基于浏览器窗口

滚动监听↑

 

离开检测 ↑

进入页面时会检测一遍有没有符合条件的元素 ↑

点击导航滚动跳转↑

 

 

基于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

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值