如何使用 js 和 css 制作关闭下拉菜单的动画?

How to Animate closing dropdown menu with js and css?

我有一个包含子菜单项的菜单,我设法在打开下拉菜单时使用 css 关键帧创建了一个简单的动画,但是当下拉菜单关闭时我无法这样做。下拉菜单关闭时如何添加动画?如您所见,当您关闭下拉菜单时,没有过渡,它会立即消失。

旧片段

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>

编辑片段:我决定不使用 css 关键帧,只使用最大高度进行过渡。这让我更容易做出改变,我仍然只是一个初学者,只是坚持简单的东西。但是,当您在项目之间切换时,它仍然没有播放任何动画。我看到 @EmielZuurbier 的解决方案即使在从一个项目切换到另一个项目时也会添加动画,我如何才能将其添加到我修改后的代码中?

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>

有多种方法可以实现这一点。因为您使用 display 属性 显示和隐藏下拉列表,所以我们需要坚持使用动画。

animateToBottom 个关键帧创建一个新的 class。此 class 应添加 具有 show class 的元素应动画化后。

只有在“out”动画结束后才应该删除 show class。通过 animationend 事件,我们可以看到动画何时结束,因此我们可以隐藏下拉菜单。

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>

这是一个不同的解决方案,它涉及 CSS 转换 CSS 自定义属性 .

通过这种方法,手风琴菜单部分最初设置为 .hide

然后在 .hide.show 之间切换(使用 .classList.toggle())这些部分。

  • a class of .hide 表示 height0
  • a .show 的 class 表示 heightvar(--openHeight)

height 将在这两个值之间设置动画。

var(--openHeight) 是针对 每个 menuItemData 单独计算的 - 它等同于 menuItemData.scrollHeight.

通过这种技术,我们可以在 0 和 CSS 无法猜测的值之间实现平滑的 CSS 过渡,但 JavaScript 可以很容易地告诉我们。


工作示例:

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>


进一步阅读: