Created
March 7, 2025 11:03
-
-
Save benhodgson87/7a61e18ba07e0d93578be283c57cbc25 to your computer and use it in GitHub Desktop.
Astro Hamburger Menu
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
--- | |
import { siteName } from "~/config/config"; | |
const links = [ | |
{ | |
text: 'Home', | |
href: '/' | |
}, | |
{ | |
text: 'About', | |
href: '/' | |
}, | |
{ | |
text: 'Contact us', | |
href: '/' | |
} | |
]; | |
--- | |
<nav class="nav" data-menu> | |
<div class="nav-inner"> | |
<header class="header"> | |
<a class="title" href="/">{siteName}</a> | |
<button class="nav-menu-handle" data-menu-handle aria-label="Toggle menu"> | |
<span class="nav-menu-icon"> | |
<span class="layer"></span> | |
<span class="layer"></span> | |
<span class="layer"></span> | |
<span class="layer"></span> | |
</span> | |
</button> | |
</header> | |
<ul class="nav-menu-items"> | |
{ | |
links.map(({ text, href }) => ( | |
<li class="nav-menu-item"> | |
<a href={href}>{text}</a> | |
</li> | |
)) | |
} | |
</ul> | |
</div> | |
</nav> | |
<style> | |
.nav { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
padding: 0 0 0 var(--spacing-sm); | |
background-color: var(--color-bg-dark); | |
color: var(--color-text-body-on-dark); | |
} | |
.nav-inner { | |
width: 100%; | |
max-width: var(--dimension-max-content-width); | |
} | |
.header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 0; | |
} | |
.title { | |
font-size: var(--typography-h1-font-size); | |
font-weight: 400; | |
line-height: var(--typography-h1-line-height); | |
} | |
.menu-icon { | |
width: 40px; | |
height: 40px; | |
color: #fff; | |
} | |
.nav-menu-handle { | |
margin: 0; | |
padding: var(--spacing-sm); | |
border: 0; | |
background-color: transparent; | |
cursor: pointer; | |
} | |
.nav-menu-icon, | |
.nav-menu-icon .layer { | |
pointer-events: none; | |
} | |
.nav-menu-icon { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
width: 25px; | |
height: 25px; | |
-webkit-transform: rotate(0deg); | |
-moz-transform: rotate(0deg); | |
-o-transform: rotate(0deg); | |
transform: rotate(0deg); | |
-webkit-transition: .5s ease-in-out; | |
-moz-transition: .5s ease-in-out; | |
-o-transition: .5s ease-in-out; | |
transition: .5s ease-in-out; | |
} | |
.nav-menu-icon .layer { | |
display: block; | |
position: absolute; | |
height: 4px; | |
width: 100%; | |
background: var(--color-text-body-on-dark); | |
border-radius: 9px; | |
opacity: 1; | |
left: 0; | |
-webkit-transform: rotate(0deg); | |
-moz-transform: rotate(0deg); | |
-o-transform: rotate(0deg); | |
transform: rotate(0deg); | |
-webkit-transition: .25s ease-in-out; | |
-moz-transition: .25s ease-in-out; | |
-o-transition: .25s ease-in-out; | |
transition: .25s ease-in-out; | |
} | |
.nav-menu-icon .layer:nth-child(1) { | |
top: 0px; | |
} | |
.nav-menu-icon .layer:nth-child(2), | |
.nav-menu-icon .layer:nth-child(3) { | |
top: 10px; | |
} | |
.nav-menu-icon .layer:nth-child(4) { | |
top: 20px; | |
} | |
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(1) { | |
top: 10px; | |
width: 0%; | |
left: 50%; | |
} | |
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(2) { | |
-webkit-transform: rotate(45deg); | |
-moz-transform: rotate(45deg); | |
-o-transform: rotate(45deg); | |
transform: rotate(45deg); | |
} | |
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(3) { | |
-webkit-transform: rotate(-45deg); | |
-moz-transform: rotate(-45deg); | |
-o-transform: rotate(-45deg); | |
transform: rotate(-45deg); | |
} | |
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(4) { | |
top: 10px; | |
width: 0%; | |
left: 50%; | |
} | |
.nav-menu-items { | |
display: none; | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
} | |
nav[data-menu="true"] .nav-menu-items { | |
display: block; | |
} | |
.nav-menu-item a { | |
display: block; | |
padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) 0; | |
color: var(--color-text-body-on-dark); | |
text-decoration: none; | |
} | |
@media (--breakpoint-tablet) { | |
.nav { | |
padding: 0 var(--spacing-md); | |
} | |
.header { | |
padding: 0; | |
} | |
.nav-inner { | |
display: inline-flex; | |
justify-content: space-between; | |
} | |
.nav-menu-items, | |
nav[data-menu="true"] .nav-menu-items { | |
display: inline-flex; | |
list-style: none; | |
} | |
.nav-menu-item { | |
margin-left: var(--spacing-md); | |
padding: 0; | |
} | |
.nav-menu-icon { | |
display: none; | |
} | |
} | |
</style> | |
<script> | |
const menuAttribute = 'data-menu'; | |
const handleAttribute = 'data-menu-handle'; | |
const toggleOrSetMenuState = (state?: boolean) => { | |
const menu = document.querySelector(`[${menuAttribute}]`); | |
if (!menu) return console.error(` [${menuAttribute}] not found in document`); | |
if (typeof state === 'boolean') { | |
return menu.setAttribute(menuAttribute, String(state)); | |
} | |
return menu.setAttribute( | |
menuAttribute, String(menu.getAttribute(menuAttribute) === 'true' ? false : true) | |
); | |
} | |
const handleMenuButtonClick = (event: MouseEvent) => { | |
if ((event.target as HTMLElement).hasAttribute(handleAttribute)) { | |
return toggleOrSetMenuState(); | |
} | |
} | |
const handleClickOutside = (event: MouseEvent) => { | |
const menu = document.querySelector(`[${menuAttribute}]`); | |
if (!menu) return console.error(`[${menuAttribute}] not found in document`); | |
if (!(event.target instanceof Node)) return; | |
if (!menu.contains(event.target) && menu.getAttribute(menuAttribute) === 'true') { | |
return toggleOrSetMenuState(false); | |
} | |
} | |
document.addEventListener("click", (event: MouseEvent) => { | |
handleMenuButtonClick(event); | |
handleClickOutside(event); | |
}); | |
window.addEventListener("scroll", () => (window.scrollY > 50) && toggleOrSetMenuState(false)); | |
window.addEventListener("resize", () => toggleOrSetMenuState(false)); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment