Here’s what we are going to build:
I’ll explain only the relevant code below. If you want to see the full code, check the codepen above.
The HTML
<body>
<div class="menu-sidebar">
<div class="sidebar-content">
<button class="close-menu-button">
<!-- close button icon -->
</button>
</div>
</div>
<div class="overlay"></div>
<div class="main-page">
<div class="header">
<button class="menu-button">
<!-- menu icon -->
</button>
</div>
<h1>Page Title</h1>
<p>Lorem ipsum dolor sit amet...</p>
</div>
</body>
This HTML consists of four main parts:
- The sidebar:
.menu-sidebar
- The main page section:
.main-page
- The button that opens the sidebar:
.menu-button
- And the overlay displayed on top of the main page when the sidebar is open:
.overlay
We have two states for this UI: idle and sidebar-open states. We specify which state is on by setting a class on the <body>
– it can be any wrapper element, but I’m using body
for this example. For this demo I used sidebar-open
class for the sidebar-open state.
We will define the styling for each state using CSS, and when the sidebar-open
class is toggled on body
, the correct styling would be applied, which will be animated with transition
.
Here’s an example for defining styles for each state:
.main-page {
// Styling for the main page when in idle state
}
body.sidebar-open .main-page {
// Styling for the main page when in sidebar-open state
}
The CSS
/* Sidebar-open state for body */
body.sidebar-open {
overflow: hidden;
}
/* Idle state for the sidebar */
.menu-sidebar {
width: 500px;
height: 100%;
overflow: auto;
position: fixed;
left: 0;
top: 0;
z-index: 2;
transition: 0.5s ease translate;
translate: -100% 0;
will-change: transform;
}
/* Sidebar-open state for the sidebar */
body.sidebar-open .menu-sidebar {
translate: 0 0;
}
/* Idle state for the main page */
.main-page {
transition: 0.5s ease translate;
will-change: transform;
}
/* Sidebar-open state for the main page */
body.sidebar-open .main-page {
translate: 500px 0;
}
/* Idle state for the overlay */
.overlay {
opacity: 0;
pointer-events: none;
background: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
transition: 0.5s ease opacity;
}
/* Sidebar-open state for the overlay */
body.sidebar-open .overlay {
opacity: 1;
pointer-events: all;
}
/* When on tablet-size screen, make the sidebar open in full width above the main page */
@media (max-width: 768px) {
.menu-sidebar {
width: 100%;
}
body.sidebar-open .main-page {
translate: 0 0;
}
}
Let’s see how the elements will be in each state.
The sidebar (.menu-sidebar
):
- We don’t want the sidebar to be part of the page flow, so we are positioning it with
position: fixed
. - To make it appear above the main page when open, we need to set its
z-index
to something bigger than.overlay
and the main page. So we set itz-index: 2
. - I’ve chosen its width to be
500px
but you can choose any width you want. - In idle state, the sidebar should be positioned outside the viewport. Setting
translate: -100% 0
will move it to the left by its full width. - For better performance, we will animate the sidebar using
translate
, and to make that even better, we need to promote the element to a new layer. That’s whatwill-change
does. - In sidebar-open state, the sidebar
translate
will be0 0
which means it will move back to its original position, and withtransition
it will appear as animating to the right.
The main page (.main-page
):
- We don’t need to do anything when in idle state. We just need to make sure it has
transition
andwill-change
for animation purposes. - But when in sidebar-open state, this element needs to move to the right by the same width as the sidebar, which is
500px
in this case.
The overlay (.overlay):
- It’s a fixed element with full width and height to cover the whole screen. It also has a background with transparency to see the main page below it
background: rgba(0, 0, 0, 0.5)
. - It should be hidden when in idle state. We are using
opacity: 0
here and notdisplay: none
because we want to animate it withtransition: 0.5s ease opacity
. - An element with
opacity: 0
still exists on the page but not seen. So clicking anything on the page would not work because you will be clicking on.overlay
. To fix this, we setpointer-events: none
, which disables all mouse and touch events on it. - When in sidebar-open state, we just need to show it and enable clicking on it (so it closes the sidebar when you click on it). To do this, we set
opacity: 1
andpointer-events: all
.
The JavaScript
The last step to make this work is toggling sidebar-open
class on body when the menu button, overlay, or close button is clicked.
const menuButton = document.querySelector('.menu-button')
const overlay = document.querySelector('.overlay')
const closeMenuButton = document.querySelector('.close-menu-button')
menuButton.addEventListener('click', toggleMenu)
overlay.addEventListener('click', toggleMenu)
closeMenuButton.addEventListener('click', toggleMenu)
function toggleMenu() {
document.body.classList.toggle('sidebar-open')
}