I have a sidebar menu which has an indicator that shows the button which is selected.
The result I was expecting was that the indicator moved but the li elements wouldn’t. But turns out they both move. I know the way I coded it is very unnecessary but I don’t know how to do it otherwise. Can someone explain to me why the li elements move, how can I make my code more efficient and how to fix my problem?
const menuButtonElements = document.querySelectorAll('#menu-sdbr-list-item button');
const menuIndicatorElement = document.getElementById('menu-sdbr-list-indicator');
const menuSidebarUlElement = document.getElementById('menu-sdbr-list');
let selectedButtonIndex = 0;
menuButtonElements.forEach((value, index) => {
value.addEventListener('click', () => {
selectedButtonIndex = index;
menuIndicatorElement.setAttribute('style', `
margin-bottom: ${(index + 1) * -65}px;
`)
menuButtonElements.forEach(value => {
value.setAttribute('style', `
color: var(--color-surface-300);
`)
})
menuButtonElements[index].setAttribute('style', `
color: white;
`)
})
})
@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import "https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round";
body {
--color-primary-100: rgb(105, 54, 245);
--color-primary-200: rgb(128, 78, 247);
--color-primary-300: rgb(148, 100, 249);
--color-primary-400: rgb(166, 122, 251);
--color-primary-500: rgb(183, 144, 252);
--color-primary-600: rgb(199, 165, 253);
--color-surface-100: rgb(0, 0, 0);
--color-surface-200: rgb(30, 30, 30);
--color-surface-300: rgb(53, 53, 53);
--color-surface-400: rgb(78, 78, 78);
--color-surface-500: rgb(105, 105, 105);
--color-surface-600: rgb(133, 133, 133);
--color-surface-mixed-100: rgb(26, 22, 37);
--color-surface-mixed-200: rgb(40, 35, 48);
--color-surface-mixed-300: rgb(63, 58, 70);
--color-surface-mixed-400: rgb(88, 83, 94);
--color-surface-mixed-500: rgb(113, 109, 119);
--color-surface-mixed-600: rgb(140, 136, 144);
--color-primary-100-mix: 105, 54, 245;
--color-primary-500-mix: 183, 144, 252;
font-family: Fira Sans Condensed, Arial;
margin: 0;
}
.menu-sidebar-overlay {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
}
.menu-sidebar {
position: relative;
height: 100%;
width: 35vw;
background-color: var(--color-primary-100);
display: flex;
}
.menu-sidebar .rectangle {
width: 15%;
height: 100%;
background-color: var(--color-surface-300);
}
.menu-sdbr-title {
display: inline;
color: var(--color-primary-200);
font-size: 40px;
margin: 15px 0 0 10px;
color: white;
}
.menu-sdbr-contents {
width: 100%;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 10px;
}
.menu-sdbr-list {
position: absolute;
width: 100%;
padding: 0;
margin: 90px 0 0 0;
list-style-type: none;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
flex: 1;
height: 100%;
}
.menu-sdbr-list-indicator {
margin-bottom: -65px;
width: 100%;
height: 60px;
background-color: var(--color-surface-300);
z-index: 250;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: margin-bottom 150ms;
}
.menu-sdbr-list-item button {
position: relative;
background-color: transparent;
border: 0 solid rgba(0, 0, 0, 1);
width: 100%;
padding: 0 15px;
height: 60px;
text-align: left;
display: flex;
align-items: center;
color: var(--color-surface-300);
z-index: 300;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
font-size: 15px;
font-weight: 400;
z-index: 300;
/*
border: 1px solid rgba(0, 0, 0, 1);
border-right: none;
*/
transition: color 150ms;
}
.menu-sdbr-list-item button .material-icons {
margin-right: 15px;
font-size: 20px;
}
<nav class="menu-sidebar-overlay">
<div class="menu-sidebar">
<div class="menu-sdbr-contents">
<h1 class="menu-sdbr-title">MENU</h1>
<ul class="menu-sdbr-list" id="menu-sdbr-list">
<li class="menu-sdbr-list-indicator" id="menu-sdbr-list-indicator">
</li>
<li class="menu-sdbr-list-item" id="menu-sdbr-list-item">
<button>
<span class="material-icons">dark_mode</span>
Dark Mode
</button>
</li>
<li class="menu-sdbr-list-item" id="menu-sdbr-list-item">
<button>
<span class="material-icons">settings</span>
Settings
</button>
</li>
<li class="menu-sdbr-list-item" id="menu-sdbr-list-item">
<button>
<span class="material-icons">logout</span>
Log Out
</button>
</li>
</ul>
</div>
<div class="rectangle"></div>
</div>
</nav>
2
Answers
Rather than move an absolutely positioned element up and down, it would be easier to add and remove a class to the parent
li
and then add the background to that and highlight to the button based on the class.This also has the added benefit of allowing you to add that class to one of the
li
if you want it pre-selected, in the example below, I have added it to the firstli
.Sorry, just realised you had an animation on your background for it to slide between the clicked elements – the above answer wouldn’t do that, instead you would need to use absolute positioning to move the highlight without the rest of it moving:
The initial
margin-top
is based on the number of elements above the middle element there is – as there are three elements and each element is 60px height with 5px gap, there is one element above therefore you margin -65px. If there were 4 elements there would be 1.5 elements above so 1.5 * -65.If you change the -65 in the css, you will also need to change it in the js
A bit hacky, but is this the kind of functionality you’re looking for?