I am trying to rephrase my question and will go through all the steps i did and especially where i failed. I don't have a deep knowledge of JS but the will to learn by practice as well as the help of the community.
I stumbled across this answer and realized the benefit. Since i don't want to use jQuery i started to rewrite it in JS.
Reference jQuery code from @zzzzBov :
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on('focusout', function () {
$(this).removeClass('active');
});
My JS code:
var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
navToggle.addEventListener('click', function() {
this.focus();
navMenu.classList.toggle('js-site-nav--open');
});
navMenu.addEventListener('blur', function() {
this.classList.remove('js-site-nav--open');
}, true);
Opening the menu works, the problem is that it will only close on 'click' outside of the menu if the focused element (Menu) is clicked once before:
var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
navToggle.addEventListener('click', function() {
this.focus();
navMenu.classList.toggle('js-site-nav--open');
});
navMenu.addEventListener('blur', function() {
this.classList.remove('js-site-nav--open');
}, true);
.c-site-nav {
color: black;
list-style-type: none;
padding-top: 20px;
position: fixed;
overflow: hidden;
top: 0;
right: -200px;
width: 200px;
height: 100%;
transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000);
opacity: .9;
background-color: green;
}
.js-site-nav--open {
right: 0;
}
.c-site-nav-btn:hover {
cursor: pointer;
background-color: red;
}
.c-site-nav-btn {
position: fixed;
top: 20px;
right: 20px;
border: 0;
outline: 0;
background-color: black;
position: fixed;
width: 40px;
height: 40px;
}
.c-site-nav-btn__line {
width: 20px;
height: 2px;
background-color: white;
display: block;
margin: 5px auto;
}
<button class="c-site-nav-btn js-site-nav-btn--toggle">
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
</button>
<nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation">
<ul class="c-site-nav__menu">
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a>
</li>
<li>SUBMENU
<ul>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
</ul>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a>
</li>
</ul>
</nav>
The first is that the link in the dialog isn't clickable. Attempting to click on it or tab to it will lead to the dialog closing before the interaction takes place. This is because focusing the inner element triggers a focusout event before triggering a focusin event again.
The fix is to queue the state change on the event loop. This can be done by using setImmediate(...), or setTimeout(..., 0) for browsers that don't support setImmediate. Once queued it can be cancelled by a subsequent focusin:
The second issue is that the dialog won't close when the link is pressed again. This is because the dialog loses focus, triggering the close behavior, after which the link click triggers the dialog to reopen.
Similar to the previous issue, the focus state needs to be managed. Given that the state change has already been queued, it's just a matter of handling focus events on the dialog triggers:
Reference jQuery code from @zzzzBov :
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on({
focusout: function () {
$(this).data('timer', setTimeout(function () {
$(this).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('timer'));
}
});
$('a').on({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
My JS code:
var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
var navLink = document.getElementsByClassName('js-site-nav__item')[0];
navToggle.addEventListener('click', function() {
this.focus();
navMenu.classList.toggle('js-site-nav--open');
});
navMenu.addEventListener('focus', function() {
this.blur(function() {
setTimeout(function() {
this.classList.remove('js-site-nav--open');
}.bind(this), 0);
});
this.focus(function() {
clearTimeout();
});
});
navLink.addEventListener('blur', function() {
navLink.blur(function() {
setTimeout(function() {
navMenu.classList.remove('js-site-nav--open');
}.bind(), 0);
});
navLink.focus(function() {
clearTimeout();
});
});
Opening the menu still works, but closing on click outside stoped working, after research i figured that blur and focus are the right methods but i guess i am missing something essential.
var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
var navLink = document.getElementsByClassName('js-site-nav__item')[0];
navToggle.addEventListener('click', function() {
this.focus();
navMenu.classList.toggle('js-site-nav--open');
});
navMenu.addEventListener('focus', function() {
this.blur(function() {
setTimeout(function() {
this.classList.remove('js-site-nav--open');
}.bind(this), 0);
});
this.focus(function() {
clearTimeout();
});
});
navLink.addEventListener('blur', function() {
navLink.blur(function() {
setTimeout(function() {
navMenu.classList.remove('js-site-nav--open');
}.bind(), 0);
});
navLink.focus(function() {
clearTimeout();
});
});
.c-site-nav {
color: black;
list-style-type: none;
padding-top: 20px;
position: fixed;
overflow: hidden;
top: 0;
right: -200px;
width: 200px;
height: 100%;
transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000);
opacity: .9;
background-color: green;
}
.js-site-nav--open {
right: 0;
}
.c-site-nav-btn:hover {
cursor: pointer;
background-color: red;
}
.c-site-nav-btn {
position: fixed;
top: 20px;
right: 20px;
border: 0;
outline: 0;
background-color: black;
position: fixed;
width: 40px;
height: 40px;
z-index:9999;
}
.c-site-nav-btn__line {
width: 20px;
height: 2px;
background-color: white;
display: block;
margin: 5px auto;
}
<button class="c-site-nav-btn js-site-nav-btn--toggle">
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
</button>
<nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation">
<ul class="c-site-nav__menu">
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a>
</li>
<li>SUBMENU
<ul>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
</ul>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a>
</li>
</ul>
</nav>
I am sure there is still a lot i have to learn, but help would be much appreciated. Thanks a lot guys.
You could set the focus on navmenu
as soon as it is displayed. If the user clicks outside of it, the blur
event would be triggered and the menu would be removed. Since clicking on the links also triggers the blur
event, we have to keep the menu on the screen when the users clicks anywhere inside of the menu. This can be monitored with a isMouseDown
flag.
Here is an enhanced version of the code snippet given in Part 1 of your question.
var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
var isMouseDown = false;
navToggle.addEventListener('click', function() {
this.focus();
navMenu.classList.toggle('js-site-nav--open');
navMenu.focus();
});
navMenu.addEventListener('mousedown', function() {
isMouseDown = true;
});
navMenu.addEventListener('mouseup', function() {
isMouseDown = false;
});
navMenu.addEventListener('mouseleave', function() {
isMouseDown = false;
});
navMenu.addEventListener('blur', function() {
if (!isMouseDown) {
navMenu.classList.remove('js-site-nav--open');
}
}, true);
.c-site-nav {
color: black;
list-style-type: none;
padding-top: 20px;
position: fixed;
overflow: hidden;
top: 0;
right: -200px;
width: 200px;
height: 100%;
transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000);
opacity: .9;
background-color: green;
}
.js-site-nav--open {
right: 0;
}
.c-site-nav-btn:hover {
cursor: pointer;
background-color: red;
}
.c-site-nav-btn {
position: fixed;
top: 20px;
right: 20px;
border: 0;
outline: 0;
background-color: black;
position: fixed;
width: 40px;
height: 40px;
}
.c-site-nav-btn__line {
width: 20px;
height: 2px;
background-color: white;
display: block;
margin: 5px auto;
}
<button class="c-site-nav-btn js-site-nav-btn--toggle">
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
<span class="c-site-nav-btn__line"></span>
</button>
<nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation">
<ul class="c-site-nav__menu">
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a>
</li>
<li>SUBMENU
<ul>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
</li>
</ul>
</li>
<li>
<a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a>
</li>
</ul>
</nav>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With