I’m building a small sidebar
element to a web page which has multiple hidden .sidebar-contents
. What it does, is reveal .sidebar-contents
within a few seconds of delay between each items whenever I click on the button. Then hides the content in reverse order right after clicking the button again.
It worked just fine in my first try, but when I try it multiple times, it looks like class is being added twice in the sidebar
. I bet I’m doing something wrong here but I don’t have a clue.
Any help would be appreciated.
const SIDEBAR_DELAY_TIME = 150;
function handleSidebar() {
const btn = document.querySelector('button');
const sidebar = document.querySelector('.sidebar');
const contents = document.querySelectorAll('.sidebar-element');
if (!btn || !sidebar) return;
btn.addEventListener('click', () => {
if (!sidebar.classList.contains('active')) {
sidebar.classList.add('active');
contents.forEach((item, i) => {
setTimeout(() => {
item.classList.add('active');
}, i * SIDEBAR_DELAY_TIME);
});
} else {
const keys = Array.from({
length: contents.length
}, (_, i) => i);
keys.reverse();
keys.forEach((key, i) => {
setTimeout(() => {
contents[key].classList.remove('active');
}, i * SIDEBAR_DELAY_TIME);
if (key === 0) {
contents[0].addEventListener('transitionend', () => {
sidebar.classList.remove('active');
});
}
});
}
});
}
handleSidebar();
:root {
--transition: 0.3s ease;
}
.sidebar {
width: 450px;
transform: translateY(55px);
transition: transform var(--transition), opacity var(--transition);
opacity: 0;
}
.sidebar.active {
transform: translateY(0);
opacity: 1;
}
.sidebar-element {
padding: 16px;
border: 1px solid #333;
transform: translateY(64px);
transition: transform var(--transition), opacity var(--transition);
opacity: 0;
}
.sidebar-element.active {
transform: translateY(0);
opacity: 1;
}
<button>click me</button>
<aside class="sidebar">
<div class="sidebar-element">
Element A
</div>
<div class="sidebar-element">
Element B
</div>
<div class="sidebar-element">
Element C
</div>
<div class="sidebar-element">
Element D
</div>
</aside>
2
Answers
The problem is here:
You are adding the
transitionend
listener multiple times. And the event is triggered after animations in both directions end. This ensues chaos.You need to remove the listener after the transition ends
As mentioned in the comments, the transition-end is attached each time the ‘set inactive’ block is executed. That means that not only it is attached multiple times, but also that it will run for the ‘set active’ part after that.
One solution would be to add it once, not in the click event. Also below is a quickly gobbled up suggestion to prevent repetition of code (might need some tweaking)
Amongst others, by introducing a helper function to set the class of an element to whatever the status is (with ‘toggle’ )