CSS 转换未按预期触发
CSS Transition is not being triggered as expected
下面的 fiddle 包含负责展开 可折叠菜单的代码。它类似于 Bootstrap 的 Collapse 组件。它是这样工作的:
- 菜单首先隐藏
display: none
。
display: none
已删除,使菜单可见,因此我们可以检索并存储其当前高度以备后用。
- 菜单为
transition
CSS 属性 配置 height
属性,现在设置为 0
.
- 菜单的
height
设置为我们在 步骤 2 中检索到的值。
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/
下面的 fiddle 包含负责展开 可折叠菜单的代码。它类似于 Bootstrap 的 Collapse 组件。它是这样工作的:
- 菜单首先隐藏
display: none
。 display: none
已删除,使菜单可见,因此我们可以检索并存储其当前高度以备后用。- 菜单为
transition
CSS 属性 配置height
属性,现在设置为0
. - 菜单的
height
设置为我们在 步骤 2 中检索到的值。 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/