Skip to content

Instantly share code, notes, and snippets.

@derekjohnson
Created January 14, 2019 11:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save derekjohnson/9d1ad375c83041a15e9ff87031a6cbe5 to your computer and use it in GitHub Desktop.
Save derekjohnson/9d1ad375c83041a15e9ff87031a6cbe5 to your computer and use it in GitHub Desktop.
Accessible performant off canvas sliding nav
/* DOM nodes */
var mainNav = document.querySelector('.js-main-nav'),
navigationList = document.querySelector('.js-navigation-list'),
navigationLinks = document.querySelectorAll('.js-navigation-link'),
navDetector = document.querySelector('.js-nav-detection');
/* Let the browser know it might need to help with the paint perf */
var hintBrowser = function() {
navigationList.style.willChange = 'transform';
};
var removeHint = function() {
navigationList.style.willChange = 'auto';
};
/* hide the menu to screenreaders, it's already visually hidden */
navigationList.setAttribute('aria-hidden', 'true');
/* Set the initial tabindex of the navigation links to -1 in small viewports */
Array.prototype.forEach.call(navigationLinks, function(link) {
if(window.getComputedStyle(link).getPropertyValue('padding-top') !== '0px') {
link.tabIndex = -1;
}
})
/* Set the tabindex of each navigation link to -1
* in small viewports so they can't be tabbed through when closed.
* This happens when the viewport changes width affecting nav layout */
var checkNavOrientation = function() {
if(window.getComputedStyle(navDetector, ':after').getPropertyValue('content')) {
Array.prototype.forEach.call(navigationLinks, function(link) {
link.tabIndex = 0;
});
} else {
Array.prototype.forEach.call(navigationLinks, function(link) {
if(!navigationList.classList.contains('is-visible')) {
link.tabIndex = -1;
}
});
}
};
navDetector.addEventListener('webkitTransitionEnd', checkNavOrientation, false);
navDetector.addEventListener('transitionend', checkNavOrientation, false);
/* create menu button */
var createButton = function() {
var button = document.createElement('button'),
text = document.createElement('span');
button.type = 'button';
button.setAttribute('aria-expanded', false);
button.setAttribute('aria-controls', 'navigation-list');
button.className = 'nav__toggle';
button.setAttribute('aria-label', 'Menu');
text.textContent = 'Menu';
text.className = 'nav__toggle-text';
text.setAttribute('aria-hidden', 'true');
button.appendChild(text);
button.addEventListener('click', toggleLinks);
button.addEventListener('mouseenter', hintBrowser);
button.addEventListener('focus', hintBrowser);
return button;
};
/* Toggle navigation links in and out of view in small viewports */
var toggleLinks = function(e) {
e.stopPropagation();
var expanded = this.getAttribute('aria-expanded') === 'true' || false,
hidden = navigationList.getAttribute('aria-hidden') === 'true' || false;
this.setAttribute('aria-expanded', !expanded);
navigationList.setAttribute('aria-hidden', !hidden);
navigationList.classList.toggle('is-visible');
Array.prototype.forEach.call(navigationLinks, function(link) {
link.tabIndex = link.tabIndex === -1 ? 0 : -1;
});
if(!navigationList.classList.contains('is-visible')) {
removeHint();
}
};
/* Close navigation when a click happens outside it */
var closeNav = function(e) {
if(e.target !== 'a.nav__link.js-navigation-link') {
navigationList.classList.remove('is-visible');
Array.prototype.forEach.call(navigationLinks, function(link) {
link.tabIndex = -1;
});
}
};
/* Add listener to close on presses outside the nav */
if(window.matchMedia('(max-width:54.375em)').matches) {
document.body.addEventListener('click', closeNav);
document.body.addEventListener('click', removeHint);
}
/* insert button into DOM and hide links */
mainNav.insertBefore(createButton(), navigationList);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment