Accessible, Progressive-Enhanced Navigation Menu with a circular animated background.
Created
September 28, 2020 01:42
-
-
Save HoangLong22/37e487eb57d3be00d973642f139656a0 to your computer and use it in GitHub Desktop.
Animated Accessible Navigation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="viewport"> | |
<header class="header" role="banner"> | |
<nav id="nav" class="nav" role="navigation"> | |
<!-- ACTUAL NAVIGATION MENU --> | |
<ul class="nav__menu" id="menu" tabindex="-1" aria-label="main navigation" hidden> | |
<li class="nav__item"><a href="#" class="nav__link">Home</a></li> | |
<li class="nav__item"><a href="#" class="nav__link">Shop</a></li> | |
<li class="nav__item"><a href="#" class="nav__link">Blog</a></li> | |
<li class="nav__item"><a href="#" class="nav__link">About</a></li> | |
<li class="nav__item"><a href="#" class="nav__link">Contact</a></li> | |
</ul> | |
<!-- MENU TOGGLE BUTTON --> | |
<a href="#nav" class="nav__toggle" role="button" aria-expanded="false" aria-controls="menu"> | |
<svg class="menuicon" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50"> | |
<title>Toggle Menu</title> | |
<g> | |
<line class="menuicon__bar" x1="13" y1="16.5" x2="37" y2="16.5"/> | |
<line class="menuicon__bar" x1="13" y1="24.5" x2="37" y2="24.5"/> | |
<line class="menuicon__bar" x1="13" y1="24.5" x2="37" y2="24.5"/> | |
<line class="menuicon__bar" x1="13" y1="32.5" x2="37" y2="32.5"/> | |
<circle class="menuicon__circle" r="23" cx="25" cy="25" /> | |
</g> | |
</svg> | |
</a> | |
<!-- ANIMATED BACKGROUND ELEMENT --> | |
<div class="splash"></div> | |
</nav> | |
</header> | |
<!-- DEMO CONTENT --> | |
<main class="main" role="main"> | |
<div class="gallery" aria-label="gallery"> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
<a href="#" class="gallery__item"></a> | |
</div> | |
</main> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const nav = document.querySelector('#nav'); | |
const menu = document.querySelector('#menu'); | |
const menuToggle = document.querySelector('.nav__toggle'); | |
let isMenuOpen = false; | |
// TOGGLE MENU ACTIVE STATE | |
menuToggle.addEventListener('click', e => { | |
e.preventDefault(); | |
isMenuOpen = !isMenuOpen; | |
// toggle a11y attributes and active class | |
menuToggle.setAttribute('aria-expanded', String(isMenuOpen)); | |
menu.hidden = !isMenuOpen; | |
nav.classList.toggle('nav--open'); | |
}); | |
// TRAP TAB INSIDE NAV WHEN OPEN | |
nav.addEventListener('keydown', e => { | |
// abort if menu isn't open or modifier keys are pressed | |
if (!isMenuOpen || e.ctrlKey || e.metaKey || e.altKey) { | |
return; | |
} | |
// listen for tab press and move focus | |
// if we're on either end of the navigation | |
const menuLinks = menu.querySelectorAll('.nav__link'); | |
if (e.keyCode === 9) { | |
if (e.shiftKey) { | |
if (document.activeElement === menuLinks[0]) { | |
menuToggle.focus(); | |
e.preventDefault(); | |
} | |
} else if (document.activeElement === menuToggle) { | |
menuLinks[0].focus(); | |
e.preventDefault(); | |
} | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// --------------------------- | |
// Vars & Helper Functions | |
// --------------------------- | |
:root { | |
--screen-width: 320px; | |
--screen-height: 560px; | |
--header-bg-color: #673AB7; | |
--splash-bg-color: #368887; | |
} | |
// calculate a circle's circumference based on radius | |
@function circumference($r){ | |
$pi: 3.141592653; | |
@return 2*$pi*$r; | |
} | |
// --------------------------- | |
// Main Navigation Menu | |
// --------------------------- | |
.nav { | |
// Toggle Button | |
&__toggle { | |
display:inline-block; | |
position:absolute; | |
z-index:10; | |
padding:0; | |
border:0; | |
background:transparent; | |
outline:0; | |
right:15px; | |
top:15px; | |
cursor:pointer; | |
border-radius:50%; | |
transition:background-color .15s linear; | |
&:hover, | |
&:focus { | |
background-color:rgba(0,0,0,.5); | |
} | |
} | |
&__menu { | |
display:flex; | |
flex-direction:column; | |
justify-content:center; | |
height:var(--screen-height); | |
position:relative; | |
z-index:5; | |
visibility:hidden; | |
} | |
&__item { | |
opacity:0; | |
transition: all .3s cubic-bezier(0.000, 0.995, 0.990, 1.000) .3s; | |
} | |
@for $i from 1 through 5 { | |
&__item:nth-child(#{$i}){ | |
transform:translateY(-40px * $i); | |
} | |
} | |
&__link { | |
color:white; | |
display:block; | |
text-align:center; | |
text-transform:uppercase; | |
letter-spacing:5px; | |
font-size:1.25rem; | |
text-decoration:none; | |
padding:1rem; | |
&:hover, | |
&:focus { | |
outline:0; | |
background-color:rgba(0,0,0,0.2); | |
} | |
} | |
} | |
// --------------------------- | |
// SVG Menu Icon | |
// --------------------------- | |
.menuicon { | |
display:block; | |
cursor:pointer; | |
color: white; | |
transform:rotate(0deg); | |
transition: .3s cubic-bezier(0.165, 0.840, 0.440, 1.000); | |
&__bar, | |
&__circle { | |
fill:none; | |
stroke: currentColor; | |
stroke-width:3; | |
stroke-linecap:round; | |
} | |
&__bar { | |
transform: rotate(0deg); | |
transform-origin:50% 50%; | |
transition: transform .25s ease-in-out; | |
} | |
&__circle { | |
transition: stroke-dashoffset .3s linear .1s; | |
stroke-dashoffset:circumference(23); // 23 is the <circle>'s radius | |
stroke-dasharray:circumference(23); | |
} | |
} | |
// --------------------------- | |
// Circular Splash Background | |
// --------------------------- | |
.splash { | |
position:absolute; | |
top:40px; | |
right:40px; | |
width: 1px; | |
height: 1px; | |
&::after { | |
content:""; | |
display:block; | |
position:absolute; | |
border-radius:50%; | |
background-color:var(--splash-bg-color); | |
// screen diameter can be 142vmax at most, | |
// circle needs to be twice that size to cover it | |
width:284vmax; | |
height:284vmax; | |
top:-142vmax; | |
left:-142vmax; | |
transform: scale(0); | |
transform-origin:50% 50%; | |
transition: transform .5s cubic-bezier(0.755, 0.050, 0.855, 0.060); | |
// will-change tells the browser we plan to | |
// animate this property in the near future | |
will-change:transform; | |
} | |
} | |
// --------------------------- | |
// Active State | |
// --------------------------- | |
.nav:target, | |
.nav--open { | |
//scale the background circle to full size | |
> .splash::after { | |
transform:scale(1); | |
} | |
//animate the menu icon | |
.menuicon { | |
color:white; | |
transform:rotate(180deg); | |
&__circle { | |
stroke-dashoffset:0; | |
} | |
&__bar:nth-child(1), | |
&__bar:nth-child(4) { | |
opacity:0; | |
} | |
&__bar:nth-child(2) { | |
transform: rotate(45deg); | |
} | |
&__bar:nth-child(3) { | |
transform: rotate(-45deg); | |
} | |
} | |
//show the nav items | |
.nav { | |
&__menu { | |
visibility:visible; | |
} | |
&__item { | |
opacity:1; | |
transform:translateY(0); | |
} | |
} | |
} | |
// --------------------------- | |
// Demo Stuff, Ignore | |
// --------------------------- | |
body { | |
background-color:#D7D7D7; | |
font-family: 'Roboto'; | |
min-height:100vh; | |
display:flex; | |
flex-direction:column; | |
justify-content:center; | |
} | |
.viewport { | |
width:var(--screen-width); | |
height:var(--screen-height); | |
margin:0 auto; | |
position:relative; | |
overflow:hidden; | |
background-color:white; | |
} | |
.header { | |
height:5rem; | |
background-color:var(--header-bg-color); | |
} | |
.main { | |
padding:20px; | |
} | |
.gallery { | |
display:grid; | |
grid-template-columns:repeat(auto-fill, minmax(130px, 1fr)); | |
grid-auto-rows: 130px; | |
grid-gap:20px; | |
&__item { | |
height:100%; | |
background-color:#D8D8D8; | |
&:hover, | |
&:focus { | |
background-color:#A4A4A4; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment