我编码了一个纯JavaScript下拉脚本,它有一些(切换)问题

时间:2019-02-27 22:04:32

标签: javascript html

我编码了一个纯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行,但这不能解决我的问题!

2 个答案:

答案 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);