jQuery 导航栏功能实现(高亮/相应菜单映射内容/无延迟二级菜单)

引入:导航栏的功能繁多,选取了几个常用的效果做个练手。

(忽然发现jQuery真的巨好用。。。对于新手的我,造轮子什么的,相当痛苦。我还有救么。。)
在这里插入图片描述
好了,总结完睡觉去了。
很多废话,看实现方法和代码就好了

功能1:鼠标滑过菜单高亮显示

实现方法:CSS :hover伪类
一般来说,用户只是在选择进入某一菜单时(未点击)会用到此功能。所以实现起来简单。

功能2:鼠标点击后,菜单保持高亮并跳转到相应内容

实现方法:
针对跳转到相应内容:这里用的是锚点定位(使用该方法时当页面的URL发生变化时,可能导致无法成功定位;改天再总结下定位方法)。
针对高亮效果: click事件+addClass(恩此类方法总是那么简单粗暴)

功能3:当屏幕滚动时,可视区域内容对应的菜单呈高亮状态

实现方法: 利用scrollTop(屏幕滚动)和可视区域显示元素的offsetTop比较,对相应菜单进行添加类名(预设样式)。

关键:我是用循环的方式添加的类名,各位大佬有没有更好的方法?(这个是不是可以用事件委托做啊,但是我试了半天bind(‘scroll’),么有反应。)

scroll事件:jQuery文档描述的是,滚动条发生变化时触发,目前我试了下,就在window/document绑定是有效的。网上还有的说,对overflow值为auto/scroll的元素也有效。真的有效,恩!
在这里插入图片描述

附上代码:

<!--No.1 主菜单,无子菜单; 功能:1.鼠标滑过高亮:CSS实现 2.点亮保持 onclick 3.对应内容显示-->
    <nav id="nav">
        <ul>
            <li data-id="0" class="active"><a href="#0">item1</a></li>
            <li data-id="1"><a href="#1">item2</a></li>
            <li data-id="2"><a href="#2">item3</a></li>
            <li data-id="3"><a href="#3">item4</a></li>
            <li data-id="4"><a href="#4">item5</a></li>
        </ul>
    </nav>
    <main id="main-content">
        <section id="0">
            <h3>第一段</h3>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
        </section>
        <section id="1">
            <h3>第二段</h3>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
        </section>
        <section id="2">
            <h3>第三段</h3>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
        </section>
        <section id="3">
            <h3>第四段</h3>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
        </section>
        <section id="4">
            <h3>第五段</h3>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
            <p>这是一个段落段落段落段落</p>
        </section>
    </main>

	<script>
        /*No.1 主菜单,无子菜单; 功能:1.鼠标滑过高亮:CSS实现 2.点亮保持 onclick 3.对应内容显示*/
        
        $(function () {
            var activeNav;
            $('#nav').on('click', 'li', function (e) {
                $('#nav li:first').removeClass('active'); //当第一次点击菜单时,将当前显示高亮类名清除。
                var that = $(this);
                if(activeNav) {
                    activeNav.removeClass('active');
                    activeNav = null;
                }
                activeNav = that;
                activeNav.addClass('active');
            })
        });

        var navFix = function () {
            var top = $(this).scrollTop(),
                offset = $('#nav').offset().top;
                navHeight = $('#nav ul').height();
                 
            if(top > offset) {
                $('#nav ul').addClass('splited');
            }else{
                $(document).unbind('srcoll', navFix);
                $('#nav ul').removeClass('splited');
            }

            var content = $('#main-content').find('section');
            for(var i = 0, len = content.length; i < len; i++) {
                if( top + navHeight > content.eq(i).offset().top) {
                    for (var j = 0; j < len; j++) {
                        var navLi = $('#nav').find('li');
                        navLi.eq(j).removeClass('active'); 
                    }
                    navLi.eq(i).addClass('active');
                }
            }
        }
        $(window).bind('scroll', navFix);
    </script>

	<style>
        ul,li{ list-style-type: none;}
        body {
            margin: 0;
            padding: 0;
        }

        /*No.1 主菜单,无子菜单*/
        #nav ul {
            width: 80%;
            margin: 0 auto;
            overflow: hidden;
        }

        #nav .splited {
            position: fixed;
            top: -1px;
            left: 0;
            right: 0;
            z-index: 999;
            margin: 0 auto;
            padding: 3px;
            width: 100%;
            opacity: .9;
            background-color:#ccc;
            text-align: center; 
        }

        #nav li {
            float: left;
            text-align: center;
        }

        #nav a {
            display: inline-block;
            width: 100px;
            height: 40px;
            line-height: 40px;
            text-decoration: none;
            color: inherit;
        }

        #nav a:hover {
            background-color: #feddcf;
        }

        .active {
            background-color: #fc5914;
            color: #929292;
        }

        #main-content section {
            padding-top: 40px;
        }   
    </style>

总结:细节处理得不好,但最近在摸索编程思想和逻辑。但是吧,细节决定成败嘛,还是要适当处理下的。

忘了还有个无延迟二级菜单…
在这里插入图片描述

功能4:无延迟二级菜单

具体功能:

  1. 鼠标经过主菜单某栏,显示相应子菜单(主菜单某栏保持高亮)
    上述功能鼠标只能在某栏内容区域内移动鼠标至其子菜单,所以

  2. 添加定时器延迟执行切换某栏及显示子菜单:在延时过程中判断鼠标是否位于子菜单中(鼠标可以任意路线进入子菜单。没错,任意,只要你足够快…);是,则不操作即维持当前显示;否则,切换下一栏。
    上述功能造成主菜单快速切换栏目时出现子菜单闪烁重叠现象(频繁触发延迟造成的),所以

  3. debounce去抖:强制一个函数在某个连续时间段内只执行一次(原理还要详细深入学习
    上述功能…解决了闪烁的问题,但是在主菜单快速切换还是会有小bug(影响用户体验,差评;都是延迟的锅),所以

  4. 预测用户鼠标进入子菜单的风骚走位,利用向量预测用户鼠标移动范围。
    移动范围预测:上一次鼠标位置,子菜单上/下边缘最左位置;三点构成的三角形;如果鼠标在三角形内,延迟操作;否,直接进行主菜单切换操作。

实现逻辑大概如上,代码如下:(HTML代码太长了,我是弄的淘宝那个菜单,但这个案例是仿慕课网的)

$(document).ready(function () {
    var sub = $('#nav-sub'),
        activeNav,
        activeSub,
        timer,
        mouseInSub = false,
        mouseTrack = [];

    sub
    .on('mouseenter',function () {
        mouseInSub = true;
    })
    .on('mouseleave',function () {
        mouseInSub = false;
    });

    //获取并储存鼠标位置
    var moveHandler = function (e) {
        mouseTrack.push({
            x: e.pageX,
            y: e.pageY
        });

        //只需保存当前位置以及上一次位置
        if(mouseTrack.length > 3) {
            //shift() 移除数组第一个元素
            mouseTrack.shift();
        }
    }

    $('#nav')
    .on('mouseenter', function () {
        sub.removeClass('none');

        //鼠标在菜单中的位置跟踪,在鼠标移除菜单时,需解绑movehandler,以免影响其他元素;
        $(document).bind('mousemove', moveHandler);
    })
    .on('mouseleave', function () {
        sub.addClass('none');

        if(activeNav) {
            activeNav.removeClass('active');
            activeNav = null;
        }

        if(activeSub) {
            activeSub.addClass('none');
            activeSub = null;
        }

        $(document).unbind('mousemove', moveHandler);
    })
    .on('mouseenter', 'li', function (e) {
        if(!activeNav) {
            activeNav = $(e.target);
            activeNav.addClass('active');
            activeSub = $('#' + activeNav.data('id'));
            activeSub.removeClass('none');
            return;
        }

        //切换频繁触发的时候,子菜单层叠切换;利用debounce去抖(定时器影响);所以在没用到之前清除计时器
        if(timer) {
            clearTimeout(timer);
        }

        //判断切换菜单是否需要延迟
        var curMousePos = mouseTrack[mouseTrack.length - 1],  //当前鼠标位置
            lastMousePos = mouseTrack[mouseTrack.length - 2], //上一次鼠标位置
            delay = needDelay(sub, curMousePos, lastMousePos); //传入sub,需要获取子菜单上下边缘坐标
        console.log(delay);
        if (delay) {
            timer = setTimeout(function () {
                if(mouseInSub) {
                    return;
                }else {
                    //一个li进入另一个li时
                    activeNav.removeClass('active');
                    activeSub.addClass('none');
                    //当前的li
                    activeNav = $(e.target);
                    activeNav.addClass('active');
                    activeSub = $('#' + activeNav.data('id'));
                    activeSub.removeClass('none');
                }
                timer = null;
            }, 300);
        }else {
            //如果鼠标不在三角形内,则直接切换菜单;
            //一个li进入另一个li时
            activeNav.removeClass('active');
            activeSub.addClass('none');
            //当前的li
            activeNav = $(e.target);
            activeNav.addClass('active');
            activeSub = $('#' + activeNav.data('id'));
            activeSub.removeClass('none');

            //与上面代码效果相同
            /*var preActiveNav = activeNav,
                preActiveSub = activeSub;
            activeNav = $(e.target);
            activeSub = $('#' + activeNav.data('id'));

            preActiveNav.removeClass('active');
            preActiveSub.addClass('none');

            activeNav.addClass('active');
            activeSub.removeClass('none');*/
        }
    })


    //预判用户鼠标移动
    //向量是否为相同方向,相同方向则当前鼠标在三角形内,否则在三角形外
    function sameSign(a, b) {
        return (a ^ b) >= 0; //利用异或判断
    }

    //定义向量:重点坐标-起点坐标
    function vector(a, b) {
        return ( {
            x: b.x - a.x,
            y: b.y - a.y
        })
    }
    
    //向量叉乘公式:v1*v2 = |v1||v2|sinθ;根据θ判断方向
    function vectorProduct(v1, v2) {
        return v1.x * v2.y - v1.y * v2.x;
    }

    //鼠标移动三角形判断
    function isTrangle(p, a, b, c) {
        var pa = vector(p, a),
            pb = vector(p, b),
            pc = vector(p, c), //三个向量

            d1 = vectorProduct(pa, pb),
            d2 = vectorProduct(pb, pc),
            d3 = vectorProduct(pc, pa); //叉乘向量的值(包括大小和反向),这里只需要方向

        return sameSign(d1, d2) && sameSign(d2, d3);
    }

    //获取子菜单上下边缘坐标,并判断当前鼠标是否在三角形内
    function needDelay(el, curMousePos, lastMousePos) {
        var offset = el.offset(),
            subTopPos = {
                x: offset.left,
                y: offset.top
            },
            subBotPos = {
                x: offset.left,
                y: offset.top + el.height()
            };

        return isTrangle(curMousePos, lastMousePos, subTopPos, subBotPos); //返回true/false
    }
});

效果如下:gif我怎么传不上来。算了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值