【问题标题】:setTimeout, jQuery operation, transitionend executed/fired randomlysetTimeout、jQuery 操作、transitionend 随机执行/触发
【发布时间】:2017-05-21 09:37:00
【问题描述】:

编辑:所以现在它不是随机的,看起来它总是无法从 .css() 方法执行(没有进行任何更改)。仍然不明白我可能犯的错误。


我正在尝试使用 jQuery 和 animate.css 对 div 的删除进行动画处理。

问题是这个动画所依赖的事件和操作是随机执行的。

此代码在 .on("click"... 处理程序内响应 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 上测试。

我可能误解了某些东西,因为它看起来很奇怪。

【问题讨论】:

  • 带有 if 语句,其中包括其他工作操作。
  • 好吧,当它不再是潜伏时,还有很多东西等着你去发现!谢谢。
  • 如果您可以使用 Stack Snippets([&lt;&gt;] 工具栏按钮)通过 runnable minimal reproducible example 更新问题来展示问题,那么人们会更容易帮助。
  • 对不起,我误读了代码。这是$(this).parents(".mdl-card"),而不是$(".mdl-card").parents()。对于那个很抱歉。 (如果只有一个,closest 可能是更好的选择。)
  • @Lucas 我已经添加了一个明确的工作解决方案。在这里给你写评论,因为它是一个未删除的答案。

标签: javascript jquery css-transitions css-animations animate.css


【解决方案1】:

即使您给setTimeout 和动画赋予相同的持续时间值,它们的回调执行顺序实际上并不能得到保证。 简化原因如下:

JS 本质上是单线程的,这意味着它一次可以执行一件事。它有一个事件循环,其中包含一堆事件队列,所有事件队列都接受网络请求、dom 事件、动画事件等的回调,并且在所有这些中,一次只运行一个并运行到结束 (Run To Completion Semantic )。这种单线程导致的进一步复杂性是,重绘和垃圾收集等事情也可能在此线程上运行,因此可能会发生额外的不可预测的延迟。

有用的资源:

这意味着,尽管您将空元素的高度过渡延迟到缩小父元素之后,但由于上述因素,无法始终保证此延迟的持续时间。因此,当setTimeout 中的回调被调用时,空元素可能不存在。

如果将setTimeout 的延迟增加到更大的值,空元素的高度动画实际上会更频繁地发生,因为这会增加zoomOut 动画结尾和@987654330 中的代码之间的差距@starting,这意味着在我们开始转换它的高度之前,空元素很可能在 DOM 中。

但是,确定此延迟的最小值值并没有真正的保证方法,因为每次都可能不同。

你必须做的是以animationendsetTimeout回调的执行顺序无关紧要的方式编写代码。


解决方案

首先,您不需要额外的空白空间,您可以在同一元素上同时执行zoomOut 动画和高度过渡。

您必须注意的一件事是您使用的 css 库已经将 .mdl-cardmin-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>

【讨论】:

    【解决方案2】:

    正如this link(由侧边栏中的某个人提供,我仍然不知道它是如何工作的?...无论如何还是谢谢你)指出,setTimeout 对于这种需要来说不够精确。它执行得太快,包含的代码找不到大约 0.3 秒之前创建的标签。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-14
      • 2013-09-12
      • 2012-05-17
      • 2021-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多