如何使用 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
表示 height
是 0
- a
.show
的 class 表示 height
是 var(--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>
进一步阅读:
我有一个包含子菜单项的菜单,我设法在打开下拉菜单时使用 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
表示height
是0
- a
.show
的 class 表示height
是var(--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>
进一步阅读: