我编码了一个纯JavaScript下拉脚本,它有一些(切换)问题。
现在,当我单击按钮时,将打开带有类标记( .is-open .in )
的下拉列表。
我再次单击触发按钮后:它提供了错误的工作。但是我单击身体或按ESC键,它可以正常关闭。
这里的问题是,脚本删除“ .is-open.in”标签,然后再次添加...当然,这之后很荒谬。
由于使用了eventListener,因此程序再次运行open(e)
。
const ClassName = {
OPEN: 'is-open',
IN: 'in',
OUT: 'out',
MENU: 'dropdown-menu',
}
const KeyCodes = {
ESC: 27
}
const DefaultConfig = {
transition: true,
}
const throwError = message => window.console.error(`bozDropdown: ${message}`);
const reflow = element => element.offsetHeight;
class bozDropdown {
constructor(id, config) {
this.id = id;
this.config = Object.assign({}, DefaultConfig, config);
this.dropdown = bozDropdown.findDropdown(this.id);
this.menu = this.dropdown.querySelector(`.${ClassName.MENU}`);
this.isOpen = false;
this.isInit = false;
this.triggers = bozDropdown.findTriggers(this.id);
this.closeEls = null;
this.open = this.open.bind(this);
this.close = this.close.bind(this);
this.onDismiss = this.onDismiss.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
init() {
if (this.isInit) {
throwError('Event listeners already added.');
return;
}
this.triggers.forEach(trigger => trigger.addEventListener('click', this.open));
this.isInit = true;
}
destroy() {
if (!this.isInit) {
throwError('Event listeners already removed.');
return;
}
this.triggers.forEach(trigger => trigger.removeEventListener('click', this.open));
this.isInit = false;
}
open(e) {
if (this.isOpen) {
return;
}
if (e)
e.preventDefault();
if (typeof this.config.beforeOpen === 'function') {
this.config.beforeOpen();
}
this.menu.style.display = 'block';
if (this.config.transition) {
this.dropdown.classList.add(ClassName.OPEN);
this.dropdown.classList.add(ClassName.IN);
this.menu.classList.add(ClassName.OPEN);
this.menu.classList.add(ClassName.IN);
this.openWithTransition();
} else {
this.dropdown.classList.add(ClassName.OPEN);
this.menu.classList.add(ClassName.OPEN);
}
document.addEventListener('mousedown', this.onDismiss);
document.addEventListener('keydown', this.handleKeyDown);
this.closeEls = [...this.dropdown.querySelectorAll('[data-dismiss="dropdown"]')];
this.closeEls.forEach(button => button.addEventListener('click', this.close));
this.isOpen = true;
if (typeof this.config.onOpen === 'function' && !this.config.transition) {
this.config.onOpen();
}
}
openWithTransition() {
const openTransitionHandler = () => {
this.menu.removeEventListener('animationend', openTransitionHandler);
if (typeof this.config.onOpen === 'function') {
this.config.onOpen();
}
};
this.menu.addEventListener('animationend', openTransitionHandler);
}
close() {
if (typeof this.config.beforeClose === 'function') {
this.config.beforeClose();
}
if (this.config.transition) {
this.dropdown.classList.remove(ClassName.IN);
this.dropdown.classList.remove(ClassName.OPEN);
this.dropdown.classList.add(ClassName.OUT);
this.menu.classList.remove(ClassName.IN);
this.menu.classList.remove(ClassName.OPEN);
this.menu.classList.add(ClassName.OUT);
this.closeWithTransition();
} else {
this.menu.style.display = 'none';
this.menu.classList.remove(ClassName.OPEN);
this.dropdown.classList.remove(ClassName.OPEN);
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
}
document.removeEventListener('mousedown', this.onDismiss);
document.removeEventListener('keydown', this.handleKeyDown);
this.closeEls.forEach(button => button.removeEventListener('click', this.close));
this.isOpen = false;
}
closeWithTransition() {
const closeTransitionHandler = () => {
this.menu.removeEventListener('animationend', closeTransitionHandler);
this.menu.style.display = 'none';
this.menu.classList.remove(ClassName.OUT);
this.dropdown.classList.remove(ClassName.OUT);
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
};
this.menu.addEventListener('animationend', closeTransitionHandler);
}
onDismiss(e) {
if (!this.menu.contains(e.target) && this.isOpen) {
this.close();
}
}
handleKeyDown(e) {
switch (e.keyCode) {
case KeyCodes.ESC:
this.close();
break;
default:
break;
}
};
static findTriggers(id) {
return [...document.querySelectorAll(`[data-toggle="dropdown"][data-target="${id}"]`)];
}
static findDropdown(id) {
return document.getElementById(id);
}
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = bozDropdown;
} else {
window.bozDropdown = bozDropdown;
}
const dropdown1 = new bozDropdown('dropdown1', {
onOpen: function() {
console.log("Açıldı 1")
},
beforeOpen: function() {
console.log("Açılacak 1")
}
});
dropdown1.init();
:root {
--duration: 350ms;
}
.dropdown {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 10rem;
padding: .5rem 0;
margin: .125rem 0 0;
font-size: 1rem;
color: #212529;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, .15);
border-radius: .25rem;
}
.dropdown.in .dropdown-menu {
animation: scaleUp var(--duration) ease-in-out;
}
.dropdown.out .dropdown-menu {
animation: scaleDown var(--duration) ease-in-out;
}
@keyframes scaleUp {
0% {
transform: scale(1.2);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes scaleDown {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.2);
opacity: 0;
}
}
<div class="dropdown" id="dropdown1">
<button data-toggle="dropdown" data-target="dropdown1">Basic Dropdown 1</button>
<div class="dropdown-menu">
<ul>
<li>Onur</li>
<li>Onur</li>
<li>Onur</li>
<li>Onur</li>
</ul>
</div>
</div>
为解决此问题,我尝试使用以下代码:if(this.isOpen) { return; }
第71行,但这不能解决我的问题!
答案 0 :(得分:1)
单击按钮元素时,不会触发您的closeTransitionHandler函数,并且下拉菜单已经可见,因为动画没有时间结束,因为调用open()时菜单再次出现(它开始褪色,但是打开了在结束动画结束之前再次播放,因此它永远不会真正结束)。这导致out类永远不会从元素中删除。
我尝试过的一个简单修复方法是设置this.isOpen = false;在closeTransitionHandler函数的末尾和close()函数的this.config.transition的else内部。 下面的代码段
close() {
if (typeof this.config.beforeClose === 'function') {
this.config.beforeClose();
}
if (this.config.transition) {
this.dropdown.classList.remove(ClassName.IN);
this.dropdown.classList.remove(ClassName.OPEN);
this.dropdown.classList.add(ClassName.OUT);
this.menu.classList.remove(ClassName.IN);
this.menu.classList.remove(ClassName.OPEN);
this.menu.classList.add(ClassName.OUT);
this.closeWithTransition();
} else {
this.menu.style.display = 'none';
this.menu.classList.remove(ClassName.OPEN);
this.dropdown.classList.remove(ClassName.OPEN);
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
this.isOpen = false;
}
document.removeEventListener('mousedown', this.onDismiss);
document.removeEventListener('keydown', this.handleKeyDown);
this.closeEls.forEach(button => button.removeEventListener('click', this.close));
}
closeWithTransition() {
const closeTransitionHandler = () => {
this.menu.removeEventListener('animationend', closeTransitionHandler);
this.menu.style.display = 'none';
this.menu.classList.remove(ClassName.OUT);
this.dropdown.classList.remove(ClassName.OUT);
if (typeof this.config.onClose === 'function') {
this.config.onClose();
}
this.isOpen = false;
};
this.menu.addEventListener('animationend', closeTransitionHandler);
}
希望有帮助!
答案 1 :(得分:1)
this.menu.addEventListener('animationend', openTransitionHandler);
this.menu.addEventListener('animationend', closeTransitionHandler);
您可以启动这些代码,并在动画之后将其删除。但是,如果在动画制作完成之前再次单击鼠标,事件将挂起,并且将发生完整的恶性循环。
激活每个"open"
和"close"
功能时,您必须关闭“ animationend” 侦听器以使用另一个功能。
结束处:
this.menu.removeEventListener('animationend', openTransitionHandler);
和;
在打开状态:
this.menu.removeEventListener('animationend', closeTransitionHandler)
;