一直覺得js學(xué)的不扎實(shí),網(wǎng)上找了項(xiàng)目練手,項(xiàng)目地址在這里。
分析
- 首先,子菜單被全部隱藏,將子菜單的樣式設(shè)為
display: block;,發(fā)現(xiàn)子菜單和他所對(duì)應(yīng)的主菜單的左端對(duì)其,后三項(xiàng)子菜單的右端超出導(dǎo)航欄的右端,說(shuō)明后面需要對(duì)子菜單的位置進(jìn)行設(shè)置; - 子菜單的小箭頭位置也需要移動(dòng)到他所對(duì)應(yīng)主菜單的中間;
- 最重要的,需要為主菜單添加鼠標(biāo)事件。
實(shí)現(xiàn)
- 最簡(jiǎn)單的,先為主菜單添加鼠標(biāo)事件,鼠標(biāo)懸停,顯示子菜單,代碼如下。
let nav = document.querySelector("#nav");
let li = nav.querySelectorAll("li");
li.forEach(function (item) {
if (item.children.length > 1) { //為有子菜單的主菜單添加鼠標(biāo)事件
item.addEventListener("mouseenter", function () {
let itemSecondChild = item.children[1];
itemSecondChild.style.display = "block";
//后續(xù)代碼
}, false);
}
})

- 在鼠標(biāo)離開主菜單后,子菜單應(yīng)消失。
li.forEach(function (item) {
if (item.children.length > 1) {
//為有子菜單的主菜單添加鼠標(biāo)事件
item.addEventListener("mouseenter", function () {
//鼠標(biāo)懸停,顯示子菜單
let itemSecondChild = item.children[1];
itemSecondChild.style.display = "block";
//后續(xù)代碼
}, false);
//鼠標(biāo)離開主菜單,子菜單消失
item.addEventListener("mouseleave", function () {
item.children[1].style.display = "none";
}, false);
}
})
注:如果當(dāng)前節(jié)點(diǎn)有子節(jié)點(diǎn),鼠標(biāo)在節(jié)點(diǎn)內(nèi)部移動(dòng)時(shí),mouseenter只會(huì)被觸發(fā)一次,mouseover移動(dòng)就會(huì)被觸發(fā);mouseleave和mouseout同理,當(dāng)鼠標(biāo)離開當(dāng)前節(jié)點(diǎn)內(nèi)的子節(jié)點(diǎn),mouseleave不會(huì)被觸發(fā),mouseout會(huì)被觸發(fā)。所以為避免多次觸發(fā),選擇mouseenter和mouseover。
這樣操作會(huì)發(fā)現(xiàn),子菜單很難被點(diǎn)到(除非手速夠快),為了客戶的體驗(yàn)感,當(dāng)然要進(jìn)行優(yōu)化,就把子菜單的顯示時(shí)間變得久一點(diǎn)。
- 更改「鼠標(biāo)離開事件」,代碼如下。
在聲明nav和li的位置添加
let timer = null;
更改「鼠標(biāo)離開事件」
item.addEventListener("mouseleave", function () {
timer = setTimeout(function () {
item.children[1].style.display = "none";
}, 300);
}, false);
還是有問(wèn)題,雖然子菜單可以被點(diǎn)擊到,但還是有問(wèn)題,他會(huì)憑空消失,console.log()測(cè)試一下。
在「鼠標(biāo)懸停事件」添加console.log("1");,在「鼠標(biāo)離開事件」添加
console.log("2");,輸出1 2 1 2,第一個(gè) 2 是我在從主菜單切換到子菜單輸出的,第二個(gè) 2 是哪里來(lái)的呢?第二個(gè) 2 是由于 setTimeout 異步執(zhí)行,在0.3秒后,進(jìn)入主線程,輸出的結(jié)果。該如何解決呢?
- 查閱資料,這可以通過(guò)「阻止冒泡」解決(可以google或百度一下)。代碼補(bǔ)充在‘后續(xù)代碼’后,見下。
//后續(xù)代碼
//阻止鼠標(biāo)離開item瞬間的冒泡行為,如果不阻止,0.3秒后,子菜單將不再顯示
//w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true
item.addEventListener("mouseover", function (event) {
event? event.cancelBubble = true : event.stopPropagation();
clearTimeout(timer);
}, false);

運(yùn)行結(jié)果顯示,雖然子菜單可以正常訪問(wèn),可是,子菜單不會(huì)正確消失,需要在「鼠標(biāo)懸停事件」對(duì)子菜單初始化。
- 對(duì)子菜單初始化。代碼補(bǔ)充在‘后續(xù)代碼’后,見下。
//后續(xù)代碼
// 將所有子菜單設(shè)為隱藏(如果不將子菜單進(jìn)行隱藏初始化,下方的 解決阻止冒泡 會(huì)一直將子菜單進(jìn)行保留??梢詫⒋诵写a注釋,查看不隱藏初始化的表現(xiàn))
for (let i = 0; i < subnav.length; i++) subnav[i].style.display = "none";
子菜單正常顯示。
- 解決最開始的需求分析:對(duì) 超出導(dǎo)航欄右側(cè)的 子菜單的位置進(jìn)行修改,并解決 子菜單的小箭頭 位置的問(wèn)題。
在定義itemSecondChild位置下方添加em(小箭頭)的定義
let em = itemSecondChild.children[0];
代碼同樣添加到‘后續(xù)代碼’后。
//對(duì)主菜單下的箭頭位置進(jìn)行設(shè)置,設(shè)置為主菜單的中間位置。
em.style.left = item.offsetWidth / 2 + "px";
//子菜單右側(cè)超出主菜單,對(duì)子菜單位置進(jìn)行調(diào)整。箭頭位置也隨之改變,也進(jìn)行設(shè)置。
if (nav.offsetWidth - item.offsetLeft < itemSecondChild.offsetWidth) {
itemSecondChild.style.right = "0";
em.style.left = item.offsetLeft - itemSecondChild.offsetLeft + item.offsetWidth / 2 + "px";
}
完整代碼在這里
如有錯(cuò)誤,歡迎指正。