引入:导航栏的功能繁多,选取了几个常用的效果做个练手。
(忽然发现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:无延迟二级菜单
具体功能:
-
鼠标经过主菜单某栏,显示相应子菜单(主菜单某栏保持高亮)
上述功能鼠标只能在某栏内容区域内移动鼠标至其子菜单,所以 -
添加定时器延迟执行切换某栏及显示子菜单:在延时过程中判断鼠标是否位于子菜单中(鼠标可以任意路线进入子菜单。没错,任意,只要你足够快…);是,则不操作即维持当前显示;否则,切换下一栏。
上述功能造成主菜单快速切换栏目时出现子菜单闪烁重叠现象(频繁触发延迟造成的),所以 -
debounce去抖:强制一个函数在某个连续时间段内只执行一次(原理还要详细深入学习)
上述功能…解决了闪烁的问题,但是在主菜单快速切换还是会有小bug(影响用户体验,差评;都是延迟的锅),所以 -
预测用户鼠标进入子菜单的风骚走位,利用向量预测用户鼠标移动范围。
移动范围预测:上一次鼠标位置,子菜单上/下边缘最左位置;三点构成的三角形;如果鼠标在三角形内,延迟操作;否,直接进行主菜单切换操作。
实现逻辑大概如上,代码如下:(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我怎么传不上来。算了。