setTimeout, jQuery 操作, transitionend executed/fired 随机
setTimeout, jQuery operation, transitionend executed/fired randomly
编辑:所以现在它不是随机的,看起来它总是无法从 .css() 方法 执行(没有变化制作)。仍然不要理解我可能犯的错误。
我正在尝试使用 jQuery 和 animate.css 动画移除 div。
问题是这个动画依赖的事件和操作随机执行。
此代码运行以响应 click
,在 .on("click"...
处理程序中:
$('section').on('click', 'button', function() {
// Remove the selected card
$(this).closest('.mdl-card')
.addClass('animated zoomOut')
.one('animationend', function() {
empty_space = $('<div id="empty-space"></div>');
empty_space.css('height', ($(this).outerHeight(true)));
$(this).replaceWith(empty_space);
});
// everything is okay until now
// setTimeOut() doesn't always execute
setTimeout(function() {
console.log("test1");
// the following doesn't always happen...
$('#empty-space')
.css({
'height': '0',
'transition': 'height .3s'
// transitionend doesn't always fire either
})
.one('transitionend', function() {
$('#empty-space').remove();
console.log("test2");
});
}, 300);
// Upgrade the DOM for MDL
componentHandler.upgradeDom();
});
/* Animate.css customization */
.animated {
animation-duration: .3s
}
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
</head>
<body>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</body>
根据页面加载,没有任何反应,有时只有第一个日志,有时只到 CSS 过渡,有时它是完整的。
已在 Firefox 和 Chromium 上测试。
我可能误解了一些东西,因为它看起来真的很奇怪。
即使您为 setTimeout
和动画赋予相同的持续时间值,实际上也无法保证其回调的执行顺序。 简化原因如下:
JS 本质上是单线程的,这意味着它一次可以执行一件事情。它有一个事件循环,里面有一堆事件队列,它们都接受网络请求、dom 事件、动画事件等事件的回调,而在所有这些事件中,一次只有一个 运行s 运行s 直到结束 (Run To Completion Semantic)。由于这种单线程性,进一步的复杂性是重绘和垃圾收集之类的事情也可能在此线程上 运行,因此可能会发生额外的不可预测的延迟。
有用的资源:
- What is the reason setTimeout is inaccurate
- JS Concurrency Model and Event Loop
- JavaScript Events & Timing in-depth
- Writing a JavaScript framework - Execution timing, beyond setTimeout
这意味着,虽然您将空元素的高度过渡延迟到父元素缩小之后,但由于上述因素,无法始终保证此延迟的持续时间。因此,当 setTimeout
中的回调被调用时,空元素可能不存在。
如果将 setTimeout
的延迟增加到更大的值,空元素的高度动画实际上会更频繁地发生,因为这会增加 zoomOut
动画之间的差距ending 和 setTimeout
starting 内的代码,这意味着在我们开始转换其高度之前,空元素很可能位于 DOM 中。
但是,并没有真正确定此延迟的最小值的方法,因为每次都可能不同。
您必须以这样的方式编写代码,使得 animationend
和 setTimeout
回调的执行顺序无关紧要。
解决方案
首先,您不需要额外的空白 space,您可以在同一个元素上同时执行 zoomOut
动画和高度过渡。
您必须注意的一件事是,您正在使用的 css 库已经将 .mdl-card
的 min-height
设置为特定值 (200px
),因此您必须在此 属性 上进行过渡,因为元素的高度可能小于此值。您还想在 height
本身上进行转换,这样您就可以删除该元素而不会出现任何卡顿。最后,您必须在 动画和过渡都完成后延迟删除元素。
这是一个可行的解决方案:
$('section').on('click', 'button', function() {
var isAnimationDone = false,
isTransitionDone = false;
var $item = $(this).closest('.mdl-card');
$item
.addClass('animated zoomOut')
.one('animationend', function() {
isAnimationDone = true;
onAllDone();
});
$item
.css({
height: 0,
'min-height': 0
})
.one('transitionend', function() {
isTransitionDone = true;
onAllDone();
});
function onAllDone() {
if (isAnimationDone && isTransitionDone) {
$item.remove();
}
}
});
.animated {
animation-duration: 300ms
}
.mdl-card {
transition: min-height 300ms, height 300ms;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
使用 Promises 这会变得更容易一些:
function animate($item, animClass) {
return new Promise((resolve) => {
$item.addClass(`animated ${animClass}`).one('animationend', resolve);
});
}
function transition($item, props) {
return new Promise((resolve) => {
$item.css(props).one('transitionend', resolve);
});
}
$('section').on('click', 'button', function() {
const $item = $(this).closest('.mdl-card');
// start animation and transition simultaneously
const zoomInAnimation = animate($item, 'zoomOut');
const heightTransition = transition($item, {
height: 0,
'min-height': 0
});
// remove element once both animation and transition are finished
Promise.all([
zoomInAnimation,
heightTransition
]).then(() => $item.remove());
});
.animated {
animation-duration: 300ms
}
.mdl-card {
transition: min-height 300ms, height 300ms;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
正如this link(边栏中有人提供的,我仍然不知道它是如何工作的?...无论如何谢谢)指出,setTimeout 对于这种需要。包含的代码执行得太快,无法找到大约 0.3 秒之前创建的标签。
编辑:所以现在它不是随机的,看起来它总是无法从 .css() 方法 执行(没有变化制作)。仍然不要理解我可能犯的错误。
我正在尝试使用 jQuery 和 animate.css 动画移除 div。
问题是这个动画依赖的事件和操作随机执行。
此代码运行以响应 click
,在 .on("click"...
处理程序中:
$('section').on('click', 'button', function() {
// Remove the selected card
$(this).closest('.mdl-card')
.addClass('animated zoomOut')
.one('animationend', function() {
empty_space = $('<div id="empty-space"></div>');
empty_space.css('height', ($(this).outerHeight(true)));
$(this).replaceWith(empty_space);
});
// everything is okay until now
// setTimeOut() doesn't always execute
setTimeout(function() {
console.log("test1");
// the following doesn't always happen...
$('#empty-space')
.css({
'height': '0',
'transition': 'height .3s'
// transitionend doesn't always fire either
})
.one('transitionend', function() {
$('#empty-space').remove();
console.log("test2");
});
}, 300);
// Upgrade the DOM for MDL
componentHandler.upgradeDom();
});
/* Animate.css customization */
.animated {
animation-duration: .3s
}
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
</head>
<body>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</body>
根据页面加载,没有任何反应,有时只有第一个日志,有时只到 CSS 过渡,有时它是完整的。
已在 Firefox 和 Chromium 上测试。
我可能误解了一些东西,因为它看起来真的很奇怪。
即使您为 setTimeout
和动画赋予相同的持续时间值,实际上也无法保证其回调的执行顺序。 简化原因如下:
JS 本质上是单线程的,这意味着它一次可以执行一件事情。它有一个事件循环,里面有一堆事件队列,它们都接受网络请求、dom 事件、动画事件等事件的回调,而在所有这些事件中,一次只有一个 运行s 运行s 直到结束 (Run To Completion Semantic)。由于这种单线程性,进一步的复杂性是重绘和垃圾收集之类的事情也可能在此线程上 运行,因此可能会发生额外的不可预测的延迟。
有用的资源:
- What is the reason setTimeout is inaccurate
- JS Concurrency Model and Event Loop
- JavaScript Events & Timing in-depth
- Writing a JavaScript framework - Execution timing, beyond setTimeout
这意味着,虽然您将空元素的高度过渡延迟到父元素缩小之后,但由于上述因素,无法始终保证此延迟的持续时间。因此,当 setTimeout
中的回调被调用时,空元素可能不存在。
如果将 setTimeout
的延迟增加到更大的值,空元素的高度动画实际上会更频繁地发生,因为这会增加 zoomOut
动画之间的差距ending 和 setTimeout
starting 内的代码,这意味着在我们开始转换其高度之前,空元素很可能位于 DOM 中。
但是,并没有真正确定此延迟的最小值的方法,因为每次都可能不同。
您必须以这样的方式编写代码,使得 animationend
和 setTimeout
回调的执行顺序无关紧要。
解决方案
首先,您不需要额外的空白 space,您可以在同一个元素上同时执行 zoomOut
动画和高度过渡。
您必须注意的一件事是,您正在使用的 css 库已经将 .mdl-card
的 min-height
设置为特定值 (200px
),因此您必须在此 属性 上进行过渡,因为元素的高度可能小于此值。您还想在 height
本身上进行转换,这样您就可以删除该元素而不会出现任何卡顿。最后,您必须在 动画和过渡都完成后延迟删除元素。
这是一个可行的解决方案:
$('section').on('click', 'button', function() {
var isAnimationDone = false,
isTransitionDone = false;
var $item = $(this).closest('.mdl-card');
$item
.addClass('animated zoomOut')
.one('animationend', function() {
isAnimationDone = true;
onAllDone();
});
$item
.css({
height: 0,
'min-height': 0
})
.one('transitionend', function() {
isTransitionDone = true;
onAllDone();
});
function onAllDone() {
if (isAnimationDone && isTransitionDone) {
$item.remove();
}
}
});
.animated {
animation-duration: 300ms
}
.mdl-card {
transition: min-height 300ms, height 300ms;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
使用 Promises 这会变得更容易一些:
function animate($item, animClass) {
return new Promise((resolve) => {
$item.addClass(`animated ${animClass}`).one('animationend', resolve);
});
}
function transition($item, props) {
return new Promise((resolve) => {
$item.css(props).one('transitionend', resolve);
});
}
$('section').on('click', 'button', function() {
const $item = $(this).closest('.mdl-card');
// start animation and transition simultaneously
const zoomInAnimation = animate($item, 'zoomOut');
const heightTransition = transition($item, {
height: 0,
'min-height': 0
});
// remove element once both animation and transition are finished
Promise.all([
zoomInAnimation,
heightTransition
]).then(() => $item.remove());
});
.animated {
animation-duration: 300ms
}
.mdl-card {
transition: min-height 300ms, height 300ms;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet" />
<link href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css" rel="stylesheet" />
<script src="https://code.getmdl.io/1.3.0/material.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<section>
<div class="mdl-card">
<button class="mdl-button mdl-js-button">Close</button>
</div>
<p>
Content to test the height of the div above
</p>
</section>
正如this link(边栏中有人提供的,我仍然不知道它是如何工作的?...无论如何谢谢)指出,setTimeout 对于这种需要。包含的代码执行得太快,无法找到大约 0.3 秒之前创建的标签。