javascript - 如何添加窗口事件以关闭打开的导航下拉菜单,但在导航项目上保持切换打开/关闭导航下拉菜单的行为?
问题描述
我已经为此工作了几天,我觉得我快到了。我继承了一个导航组件,其行为类似于任何导航——用户单击导航项以显示下拉菜单,然后单击导航项或下拉菜单将其关闭。但是,当用户单击页面上的其他任何位置时,我现在需要能够关闭任何打开的下拉菜单。
我面临的问题是我需要让我的窗口事件忽略导航。否则,单击窗口会删除用户在导航单击时关闭导航的能力。
我在下面粘贴了我的 JavaScript 代码。有人可以帮助我吗?谢谢你。
navClick() 函数已经存在。我添加了 closeMenuOnWindowClick()。
function initNav() {
const primaryNavElement = document.querySelector("#dcom-nav-primary");
const secondaryNavElement = document.querySelector("#dcom-nav-secondary");
const secondaryNavLinks = document.querySelectorAll(".dcom-nav-secondary-links");
//These variables are defined in the markup and dumped at that level. Added eslint lines below so it doesn't scream at us.
// eslint-disable-next-line
let navigationArray = [];
if(primaryNavElement){
navigationArray = primaryNavElement.getElementsByTagName("li");
}
let i;
const navClick = function() {
let navElm = this;
//Navigation buttons are relying on the navlevel attribute in the markup to determine how they behave
if(navElm,navigationArray.length,secondaryNavLinks){
if(navElm.attributes.navlevel){
if(navElm.attributes.navlevel.nodeValue == 'primary'){
//Primary Nav Click
//Reset and apply active link tab
for(i = 0; i<navigationArray.length; i++){
navigationArray[i].desktopDOMs[0].classList.remove('dcom-c-navigation-bar__li--active');
navigationArray[i].desktopDOMs[1].classList.add('dcom-c-navigation-bar__secondary-row--hidden');
}
navigationArray[navElm.index].desktopDOMs[0].classList.add('dcom-c-navigation-bar__li--active');
navigationArray[navElm.index].desktopDOMs[1].classList.remove('dcom-c-navigation-bar__secondary-row--hidden');
} else if(navElm.attributes.navlevel.nodeValue == 'secondary'){
//Secondary Nav Click
let dropdown = navElm.querySelector(".dcom-c-navigation-bar__dropdown");
if(dropdown){
for(i = 0; i<secondaryNavLinks.length; i++){
for(let ii = 0; ii<secondaryNavLinks[i].children.length; ii++){
let sDropdown = secondaryNavLinks[i].children[ii].querySelector(".dcom-c-navigation-bar__dropdown");
if(sDropdown != dropdown){
sDropdown.classList.add('dcom-c-navigation-bar__dropdown--hidden');
}
}
}
if(dropdown.children.length > 0){ //In the event the secondary dropdown has no tertiary links, do nothing, let it navigate.
// If there are only 2 items, remove flex-wrap
if(dropdown.children.length < 3) {
dropdown.style.flexWrap = 'nowrap';
}
if(dropdown.classList.contains('dcom-c-navigation-bar__dropdown--hidden')) {
dropdown.classList.remove('dcom-c-navigation-bar__dropdown--hidden');
dropdown.style.width = "auto"; //Reset width
//Handle dropdown width
//First, find out how many columns we need;
let dropdownCols = [[],[],[]];
let c = 0;
let r = 0;
for(i = 0; i < dropdown.children.length; i++){
dropdownCols[c].push(dropdown.children[i]);
r++;
if(r > 5){
r = 0;
c++;
}
}
//Then step through the links we have, find the largest, and set the largest size in each column
let colWidths = [0,0,0];
for(i = 0; i<dropdownCols.length; i++){
for(let ii = 0; ii<dropdownCols[i].length; ii++){
let linkWidth = dropdownCols[i][ii].offsetWidth;
if(linkWidth > colWidths[i])colWidths[i]=linkWidth;
}
}
//Set the width of the dropdown to the size of the largest columns
dropdown.style.width = (colWidths[0]+colWidths[1]+colWidths[2]+20)+"px";
//For IE fix we have to manually set the height. It's stupid, I know.
let linkHeight = dropdownCols[0][0].offsetHeight + 3; //get the height of one link element
//Check if its less than 6 (which wraps)
if(dropdown.children.length < 6){
//If its less than 6, make the height the height of the number of children
dropdown.style.height = (linkHeight * dropdown.children.length)+"px";
} else {
//Otherwise, set it to 6
dropdown.style.height = linkHeight * 6+"px";
}
//Check if this div is going off the screen
dropdown.style.right = "";
dropdown.style.left = "";
var dropdownRect = dropdown.getBoundingClientRect();
if((dropdownRect.left + dropdown.offsetWidth) > window.innerWidth){
dropdown.style.right = 0;
dropdown.style.left = "auto";
}
} else {
dropdown.classList.add('dcom-c-navigation-bar__dropdown--hidden');
//hide it if its open
}
}
}
}
/* Note: Since we don't have a Single Page Application here (haha), the assumption is that tertiary links will cause a page reload/navigation.
* In the event this is used in a SPA framework, can add a check here for tertiary controls, and add a click handler above.
*/
} else {
//Otherwise, using a switch based on the element id (for now! have had success with custom attributes before to determine button purpose that has less risk of conflict)
switch(navElm.id) {
default:
//Do nothing
}
}
}
};
if(primaryNavElement,secondaryNavElement,secondaryNavLinks.length){ //check all elements exist
//Find and add click handlers to all primary nav links
for(i = 0; i<primaryNavElement.children.length; i++){
if(primaryNavElement.children[i].nodeName == "LI"){ //double checking I'm grabbing list items
navigationArray[i].desktopDOMs = [];
navigationArray[i].desktopDOMs.push(primaryNavElement.children[i]); //Assign to reference array for future use
primaryNavElement.children[i].index = i; //I add an index here for reference
primaryNavElement.children[i].addEventListener('click', navClick);
//each primary nav's secondary navs
}
}
//Find all secondary links bars, store reference
for(i = 0; i<secondaryNavElement.children.length; i++){
navigationArray[i].desktopDOMs.push(secondaryNavElement.children[i]);
secondaryNavElement.children[i].index = i;
//secondaryNavElement.children[i]
}
//For all links, add handler, push to desktopDOMS
//console.log(secondaryNavLinks);
for(i = 0; i<secondaryNavLinks.length; i++){
for(let ii = 0; ii<secondaryNavLinks[i].children.length; ii++){
secondaryNavLinks[i].children[ii].index = i;
secondaryNavLinks[i].children[ii].addEventListener('click', navClick);
}
}
}
function closeMenuOnWindowClick() {
//Get all dropdowns
let menus = document.getElementsByClassName('dcom-c-navigation-bar__dropdown');
//Convert to array
menus = [...menus];
let openMenu;
//Loop through to find secondary level menu that is currently open
menus.forEach(menu => {
//If it's a secondary level nav and it's showing
if(menu.attributes.navlevel = 'secondary' && !menu.classList.contains('dcom-c-navigation-bar__dropdown--hidden')){
//Get it and label it
openMenu = menu;
openMenu.classList.add('dcom-c-navigation-bar__dropdown--hidden');
}
})
}
window.addEventListener('mouseup', function() {
closeMenuOnWindowClick();
navClick();
}, false);
}
module.exports = initNav;
解决方案
感谢stackoverflow,我在这里和那里找到了一些片段并且能够成功!
以下是我完成解决方案的方法:
// Click anywhere on the page to close an open menu.
window.addEventListener('click', function (ev) {
// Click anywhere, excluding element with a class you want to ignore, to hide the dropdown.
if (ev.target.className !== 'ignoreThisClass') {
dropdown.classList.add('addYourOwnHideClass');
}
});
推荐阅读
- c++ - 通过左值引用传递与在构造函数中通过值传递
- join - 来自同一个表的连接代码和描述,用逗号分隔
- c++ - Stack和Heap关于内存地址的问题
- javascript - 有人可以帮我解释为什么 JS 不能在 HTML 中工作吗?
- swift - 使用 Kingfisher activityIndicator 时 UICollectionViewCell 大小错误
- qt - 自定义 QML TabButton
- python-3.x - 如何从 Sentinel 获得正确/更好的图像?
- transactions - 使用 Airflow Snowflake Operator 设置“Autocommit=false”
- django - 如何设置 nginx 以正确地为 React 和 Django 提供服务
- http - 如何在返回错误之前收集尽可能多的信息