I have a menu with submenu items, i managed to create a simple animation with css keyframe when opening dropdown, but i can't do the same when dropdown is closed. How can I add animation when the dropdown is closed ? As you can see, when you close the dropdown there is no transition, it just disappears instantly.
Old Snippet
var dropdownBtn = document.querySelectorAll('.menu-btn');
//Add this for toggling dropdown
lastOpened = null;
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
menuContent.classList.toggle("show");
//Add this for toggling dropdown
if (lastOpened && lastOpened !== menuContent)
lastOpened.classList.remove("show");
lastOpened = menuContent;
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
animation:animateFromBottom .3s;
}
.drop_container.show {
display: block;
}
.drop_container > .item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
@keyframes animateFromBottom {
from{bottom:-50px;opacity:0}
to{bottom:0;opacity:1}
}
@keyframes animateToBottom {
from{bottom:0;opacity:1}
to{bottom:-50px;opacity:0}
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
I have a menu with submenu items, i managed to create a simple animation with css keyframe when opening dropdown, but i can't do the same when dropdown is closed. How can I add animation when the dropdown is closed ? As you can see, when you close the dropdown there is no transition, it just disappears instantly.
Old Snippet
var dropdownBtn = document.querySelectorAll('.menu-btn');
//Add this for toggling dropdown
lastOpened = null;
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
menuContent.classList.toggle("show");
//Add this for toggling dropdown
if (lastOpened && lastOpened !== menuContent)
lastOpened.classList.remove("show");
lastOpened = menuContent;
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
animation:animateFromBottom .3s;
}
.drop_container.show {
display: block;
}
.drop_container > .item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
@keyframes animateFromBottom {
from{bottom:-50px;opacity:0}
to{bottom:0;opacity:1}
}
@keyframes animateToBottom {
from{bottom:0;opacity:1}
to{bottom:-50px;opacity:0}
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
Edit Snippet: I decided not to use css keyframe, just transition with max-height. This makes it easier for me to make changes, I'm still just a beginner and just stick to the simple stuff. However, when you switch between items it is still not playing any animation. I see that @EmielZuurbier's solution adds animation even when switching from one item to another, how can I make this to my modified code ?
var dropdownBtn = document.querySelectorAll('.menu-btn');
//Add this for toggling dropdown
lastOpened = null;
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
if (!menuContent.classList.contains("show")) {
menuContent.classList.add("show");
menuContent.classList.remove("hide");
} else {
menuContent.classList.add("hide");
menuContent.classList.remove("show");
}
//Add this for toggling dropdown
if (lastOpened && lastOpened !== menuContent)
lastOpened.classList.remove("show");
lastOpened = menuContent;
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
overflow: hidden;
max-height: 0;
}
.drop_container.show {
max-height: 300px;
transition: max-height 0.3s ease-in;
}
.drop_container.hide {
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease-out;
}
.drop_container > .item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
Share
Improve this question
edited May 23, 2022 at 14:28
Snorlax
asked May 22, 2022 at 16:23
SnorlaxSnorlax
2932 gold badges14 silver badges50 bronze badges
5
-
N.B. You can use the
classList.toggle('my-class')
method. This removes the class if it is present and adds it if it is absent. Consequently, this:if (!menuContent.classList.contains("show")) {menuContent.classList.add("show"); menuContent.classList.remove("hide");} else {menuContent.classList.add("hide"); menuContent.classList.remove("show");}
bees two short statements without a need for conditional logic:menuContent.classList.toggle("hide"); menuContent.classList.toggle("show");
– Rounin Commented May 23, 2022 at 14:33 -
1
@Rounin-StandingwithUkraine Thanks for the suggestion, I appreciate it. I have already tried it, in fact it was the initial idea, but then I did not understand why the menu does not work as it should. with classList.toggle I end up with this:
div class="drop_container hide show"
so the main class acquires both hide and show. – Snorlax Commented May 23, 2022 at 14:39 -
It's only because you need to initialise
.drop-container
with.hide
. Your initial markup should read:<div class="drop_container hide">
. – Rounin Commented May 23, 2022 at 15:21 - I'm trying but it doesn't seem to work, if you like could you answer me with an example? I would be grateful. Thanks. – Snorlax Commented May 23, 2022 at 15:33
- 1 I've added an example below, using CSS Transitions and CSS Custom Properties. – Rounin Commented May 23, 2022 at 22:15
2 Answers
Reset to default 3There are multiple ways to achieve this. Because you're showing and hiding the drop down lists with the display
property we'll need to stick to animations.
Create a new class for the animateToBottom
keyframes. This class should be added after an element with the show
class should animate out.
Only after the "out" animation has finished should the show
class be removed. With the animationend
event we can see when our animation finishes so we can hide the dropdown.
const dropdownBtns = document.querySelectorAll('.menu-btn');
let lastOpened = null;
dropdownBtns.forEach(btn => btn.addEventListener('click', function() {
const menuContent = this.nextElementSibling;
if (lastOpened !== null) {
const target = lastOpened;
target.addEventListener('animationend', () => {
target.classList.remove('show', 'animate-out');
if (target === lastOpened) {
lastOpened = null;
}
}, {
once: true
});
target.classList.add('animate-out');
}
if (lastOpened !== menuContent) {
menuContent.classList.add('show');
lastOpened = menuContent;
}
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
animation: animateFromBottom .3s;
}
.drop_container.show {
display: block;
}
.drop_container.show.animate-out {
animation: animateToBottom .3s;
}
.drop_container>.item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
@keyframes animateFromBottom {
from {
transform: translate3d(0, 10px, 0);
opacity: 0
}
to {
transform: translate3d(0, 0, 0);
opacity: 1
}
}
@keyframes animateToBottom {
from {
transform: translate3d(0, 0, 0);
opacity: 1
}
to {
transform: translate3d(0, 10px, 0);
opacity: 0
}
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
Here is a different solution which involves CSS Transitions and CSS Custom Properties.
With this approach the accordion menu sections are initially set to .hide
.
The sections are then toggled (using .classList.toggle()
) between .hide
and .show
.
- a class of
.hide
means theheight
is0
- a class of
.show
means theheight
isvar(--openHeight)
The height
will animate between these two values.
The var(--openHeight)
is calculated individually for each menuItemData
- it is equivalent to menuItemData.scrollHeight
.
Via this technique we can enable a smooth CSS transition between 0
and a value which CSS cannot guess at, but JavaScript can readily tell us.
Working Example:
let dropdownMenuItemTitles = document.querySelectorAll('.dropdown-menu-item-title');
dropdownMenuItemTitles.forEach(menuItemTitle => {
menuItemTitle.addEventListener('click', (e) => {
const menuItemData = e.target.nextElementSibling;
menuItemData.style.setProperty('--openHeight', menuItemData.scrollHeight + 'px');
menuItemData.classList.toggle('show');
menuItemData.classList.toggle('hide');
})
});
.dropdown-menu-item-title {
background-color: #a0a0a0;
padding: 10px;
margin: 5px 0px 0px 0px;
cursor: pointer;
}
.dropdown-menu-item-title:hover {
background: #000;
color: #fff;
}
.dropdown-menu-item-data {
margin: 0;
overflow: hidden;
transition: height 0.3s ease-out;
}
.dropdown-menu-item-data.hide {
height: 0;
}
.dropdown-menu-item-data.show {
height: var(--openHeight);
}
.dropdown-submenu {
padding: 0;
background-color: #e0e0e0;
list-style-type: none;
}
.dropdown-submenu-item {
padding: 12px;
}
<dl class="dropdown-menu">
<div>
<dt class="dropdown-menu-item-title">One</dt>
<dd class="dropdown-menu-item-data hide">
<ul class="dropdown-submenu">
<li class="dropdown-submenu-item"><a href="#">Contact Us</a></li>
<li class="dropdown-submenu-item"><a href="#">Visit Us</a></li>
</ul>
</dd>
</div>
<div>
<dt class="dropdown-menu-item-title">Two</dt>
<dd class="dropdown-menu-item-data hide">
<ul class="dropdown-submenu">
<li class="dropdown-submenu-item"><a href="#">About Us</a></li>
<li class="dropdown-submenu-item"><a href="#">Visit Us</a></li>
<li class="dropdown-submenu-item"><a href="#">Opening Times</a></li>
<li class="dropdown-submenu-item"><a href="#">Contact Us</a></li>
</ul>
</dd>
</div>
<div>
<dt class="dropdown-menu-item-title">Three</dt>
<dd class="dropdown-menu-item-data hide">
<ul class="dropdown-submenu">
<li class="dropdown-submenu-item"><a href="#">About Us</a></li>
<li class="dropdown-submenu-item"><a href="#">Visit Us</a></li>
<li class="dropdown-submenu-item"><a href="#">Contact Us</a></li>
</ul>
</dd>
</div>
</dl>
</div>
Further Reading:
- https://developer.mozilla/en-US/docs/Web/CSS/Using_CSS_custom_properties