最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to Animate closing dropdown menu with js and css? - Stack Overflow

programmeradmin7浏览0评论

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
Add a ment  | 

2 Answers 2

Reset to default 3

There 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 the height is 0
  • a class of .show means the height is var(--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
发布评论

评论列表(0)

  1. 暂无评论