CSS 转换未按预期触发

CSS Transition is not being triggered as expected

下面的 fiddle 包含负责展开 可折叠菜单的代码。它类似于 Bootstrap 的 Collapse 组件。它是这样工作的:

  1. 菜单首先隐藏 display: none
  2. display: none 已删除,使菜单可见,因此我们可以检索并存储其当前高度以备后用。
  3. 菜单为 transition CSS 属性 配置 height 属性,现在设置为 0 .
  4. 菜单的 height 设置为我们在 步骤 2 中检索到的值。
  5. transitionend 事件处理程序应在转换结束后启动。

虽然菜单扩展到预期的高度,但它没有我们的过渡。我需要你的帮助来找出原因。

在这里,试试看:https://jsfiddle.net/avkdb89j/3/

一个重要观察:如果您打开检查器工具,select <nav>(菜单)并切换我们的代码在您单击按钮后添加的 height 内联样式,立即触发转换,并执行 transitionend 处理程序。这可能是竞争条件吗?高度应该在设置过渡之后改变,而不是之前。

你需要去掉 display: none 并让 css 处理更多的转换。

我把.np-collapsible设置成这个是为了让元素一直存在:

.np-collapsible:not(.np-expanded) {
  height: 0;
  overflow: hidden;
}

我将转换 class 设置为不进行任何会启动转换的更改(它仅包括转换 属性)。

然后在 JS 中,过渡的完成方式与您原来的类似,但主要区别在于我使用 menu.scrollHeight 来获取菜单的高度,以避免有额外的过渡来获得正常高度。

我还在函数中添加了收缩菜单的功能。在收缩菜单的情况下,由于 :not(.np-expanded) 选择器提前停止溢出,您必须在转换前删除 np-expanded class:none.

  // Retrieve the height of the menu
  var targetHeight = menu.scrollHeight + 'px';
  if (menu.classList.contains('np-expanded')) {
    // It's already expanded, time to contract.
    targetHeight = 0;
    menu.classList.remove('np-expanded');
    button.setAttribute('aria-expanded', false);
  }
  // Enable transition
  menu.classList.add('np-transitioning');
  menu.addEventListener('transitionend', function(event) {
    // Disable transition
    menu.classList.remove('np-transitioning');

    // Indicate that the menu is now expanded
    if (targetHeight) {
      menu.classList.add('np-expanded');
      button.setAttribute('aria-expanded', true);
    }
  }, {
    once: true
  });

  // Set the height to execute the transition
  menu.style.height = targetHeight;

这是一个工作示例:

var button = document.querySelector('.np-trigger');
var menu = document.querySelector(button.dataset.target);

button.addEventListener('click', function(event) {
  expand();
}, false);

function expand() {
  if (isTransitioning()) {
    // Don't do anything during a transition
    return;
  }

  // Retrieve the height of the menu
  var targetHeight = menu.scrollHeight + 'px';
  if (menu.classList.contains('np-expanded')) {
    // It's already expanded, time to contract.
    targetHeight = 0;
    menu.classList.remove('np-expanded');
    button.setAttribute('aria-expanded', false);
  }
  // Enable transition
  menu.classList.add('np-transitioning');
  menu.addEventListener('transitionend', function(event) {
    // Disable transition
    menu.classList.remove('np-transitioning');

    // Indicate that the menu is now expanded
    if (targetHeight) {
      menu.classList.add('np-expanded');
      button.setAttribute('aria-expanded', true);
    }
  }, {
    once: true
  });

  // Set the height to execute the transition
  menu.style.height = targetHeight;
}

function isTransitioning() {
  if (menu.classList.contains('np-transitioning')) {
    return true;
  }

  return false;
}
.np-collapsible:not(.np-expanded) {
  height: 0;
  overflow: hidden;
}

.np-transitioning {
  transition: height 0.25s ease;
}

.navigation-menu {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 4rem;
  left: 1rem;
  width: 270px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<button class="btn btn-dark np-trigger" data-target="#menu">Menu</button>

<nav id="menu" class="bg-dark navigation-menu np-collapsible">
  <a class="nav-link" href="#">Link</a>
  <a class="nav-link" href="#">Link</a>
  <a class="nav-link" href="#">Link</a>
</nav>

如前所述,原版中存在竞争条件,因为通过将 classes 设置为原样开始了多个转换。此方法通过保持简单来避免 display: none 和其他竞争条件的问题。

有趣的是,将 menu.classList.add('np-transitioning) 行移动到与删除 np-collapsible 相同的位置允许它工作。我觉得可能只是这些更改在那时基本上是批处理在一起的。原始 jquery 代码起作用的原因可能是因为它有 classes 是 added/removed,中间没有任何其他 DOM 工作。

这里是原来的更新,可以在上面提到的方法中工作 https://jsfiddle.net/5w12rcbh/

这是相同的更新,但使用 classList.replace 等方法进行了一些扩展和清理,以便一次执行更多工作。这增加了与我的原始片段相同的切换能力。 https://jsfiddle.net/bzc7uy2s/