为什么要使用鼠标行为预测
因为有时候我们把鼠标从主菜单移到子菜单(斜移)的时候会切换子菜单,但是我们的目的并不是切换子菜单,所以需要通过鼠标行为的预测来设置延时器,以便斜移鼠标到子菜单的时候,其他主菜单不切换
**重点:**预判鼠标走的方向
- 在列表中取四个点A, B, C, P
- 判断点P是否在▲ABC内
- 如果点P在▲ABC内说明用户鼠标具有一定的斜度,因此就要设置一个延时器,不改变子菜单里面的内容
- 否则,就不需要延时器
步骤
- 声明鼠标是否在子菜单的状态,来判断鼠标是否在子菜单
- document绑定鼠标移动事件处理函数mouseMove,建立mousePos数组用来保存鼠标移动过程中的A,P点
- 在isNeedTimeout函数中判断P点是否在三角形内
- 根据isNeedTimeout返回值,判断是否添加延时器
- 同时要注意,第一次进入主菜单不能出现延迟,所以要设置变量isFirst 来判断是否是第一次进入主菜单
实现的JS代码
window.onload = function () {
init();
}
function init() {
initMenu();
}
function initMenu() {
var oWrap = document.getElementsByClassName('wrap')[0],
oMenu = oWrap.getElementsByClassName('menu-list')[0],
menuItems = oMenu.getElementsByClassName('menu-item'),
oSub = oWrap.getElementsByClassName('sub')[0],
subItems = oSub.getElementsByClassName('sub-item'),
menuLen = menuItems.length,
menuItem,
subItem,
mousePos = [],
t = null,
//是否是第一次进入menu
isFirst = true,
//是否在子菜单内,在的话就不执行延时
isInSub = false;
// 鼠标移入wrap, 要记录鼠标的位置
addEvent(oWrap, 'mouseenter', function(){
addEvent(document, 'mousemove', mouseMove);
})
// 鼠标移出wrap,要把事件都清除
addEvent(oWrap, 'mouseleave', wrapMouseLeave)
// 给menu绑定鼠标移入事件
for (var i = 0; i < menuLen; i++) {
menuItem = menuItems[i];
addEvent(menuItem, 'mouseenter', menuItemMouseEnter)
}
addEvent(oSub, 'mouseenter', function () {
isInSub = true;
console.log(isInSub)
})
addEvent(oSub, 'mouseleave', function () {
isInSub = false
})
function menuItemMouseEnter(e) {
var e = e || window.event,
target = e.target || e.srcElement,
//找出当前这一项的下标
curIndex = Array.prototype.indexOf.call(menuItems, this),
posLen = mousePos.length,
curPos = mousePos[posLen - 1] || {x: 0, y: 0},
lastPos = mousePos[posLen - 2] || {x: 0, y: 0},
isDelay = isNeedTimeout(curPos, lastPos);
// console.log(isDelay,'isInSub:', isInSub);
oSub.className = 'sub';
if (t) {
clearTimeout(t);
}
if (!isFirst) {
if (isDelay) {
t = setTimeout(function () {
if(isInSub){
return
}
addActive(curIndex);
t = null;
}, 500);
} else {
addActive(curIndex);
}
} else {
addActive(curIndex);
isFirst = false;
}
}
// 添加样式
function addActive(index) {
//加样式之前先把之前的样式都清除
removeActive();
menuItems[index].className += ' active';
subItems[index].className += ' active';
}
function removeActive() {
for (var i = 0; i < menuLen; i++) {
menuItem = menuItems[i];
subItem = subItems[i];
menuItem.className = 'menu-item';
subItem.className = 'sub-item';
}
}
function mouseMove(e) {
var e = e || window.event;
mousePos.push({
x: getPagePos(e).x,
y: getPagePos(e).y
})
//只保三个点,且是最后的三个,前面多的删掉
if (mousePos.length > 3) {
mousePos.shift();
}
}
function wrapMouseLeave() {
oSub.className += ' hide';
removeActive();
clearTimeout(t);
removeEvent(document, 'mousemove', mouseMove);
}
// 如果鼠标在指定范围内移动,那么就要设置一个延时器,不改变子菜单里面的内容
function isNeedTimeout(curPos, lastPos) {
var topLeft = {
x: getStyles(oWrap, 'width') + getStyles(oWrap, 'margin-left'),
y: getStyles(oWrap, 'margin-top')
},
bottomLeft = {
x: getStyles(oWrap, 'width') + getStyles(oWrap, 'margin-left'),
y: getStyles(oSub, 'height') + getStyles(oWrap, 'margin-top')
}
return pointInTriangle({
curPos,
lastPos,
bottomLeft,
topLeft
})
}
}
function addEvent(ele, type, fn) {
if (ele.addEventListener) {
ele.addEventListener(type, fn, false);
} else if (ele.attchEvent) {
ele.attchEvent('on' + type, fn);
} else {
ele['on' + type] = fn;
}
}
function removeEvent(ele, type, fn) {
if (ele.removeEventListener) {
ele.removeEventListener(type, fn, false);
} else if (ele.detachEvent) {
ele.detachEvent('on' + type, fn);
} else {
fn = null;
}
}
function getStyles(ele, attr) {
if (window.getComputedStyle) {
if (attr) {
return parseInt(window.getComputedStyle(ele, null)[attr]);
} else {
return window.getComputedStyle(ele, null);
}
} else if (ele.currentStyle) {
if (attr) {
return parseInt(ele.currentStyle[attr]);
} else {
return ele.currentStyle;
}
}
}
//获取鼠标在元素中位置
function getPagePos(e) {
var sl = getScroll().left,
st = getScroll().top,
// 获取文档的偏移距离
shiftX = document.documentElement.clientLeft || 0,
shiftY = document.documentElement.clientTop || 0;
return {
x: e.clientX + sl - shiftX,
y: e.clientY + st - shiftY
}
}
//获取滚动条的位置
function getScroll() {
if (window.pageXOffset) {
return {
top: window.pageYOffset,
left: window.pageXOffset
}
} else {
return {
top: document.documentElement.scrollTop + document.body.scrollTop,
left: document.documentElement.scrollLeft + document.body.scrollLeft
}
}
}
var pointInTriangle = (function () {
function vec(a, b) {
return {
x: b.x - a.x,
y: b.y - a.y
}
}
function vecProduct(v1, v2) {
return v1.x * v2.y - v1.y * v2.x;
}
function sameSymbol(a, b) {
return (a ^ b) >= 0;
}
return function (opt) {
var PA = vec(opt.curPos, opt.lastPos),
PB = vec(opt.curPos, opt.topLeft),
PC = vec(opt.curPos, opt.bottomLeft),
R1 = vecProduct(PA, PB),
R2 = vecProduct(PB, PC),
R3 = vecProduct(PC, PA);
return sameSymbol(R1, R2) && sameSymbol(R2, R3);
}
})()