[英]CSS Transition fails on jQuery .load callback
我有一个函数,它使用 jQuery 将 html 加载到一个表中,然后通过回调向其中一行添加一个类。 该功能由页面上的各种 UI 驱动事件触发。 我还有一个 css 过渡规则,所以颜色应该淡入( transition: background-color 1000ms linear
)。 该函数如下所示:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).addClass('green');
});
}
加载 html 后,该类成功添加并且行颜色设置为绿色。 但是,我的 css 转换规则似乎被忽略了。
当我添加一个轻微的延迟时,即使是 10 毫秒,它也能正常工作:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
setTimeout(function() {
$(row_id).addClass('green');
}, 10);
});
}
如果提供了“完整”回调,则在执行后处理和 HTML 插入后执行。
对我来说,这表明新元素已加载到 dom 中,并应用了现有样式并准备好进行操作。 为什么转换在第一个示例中失败,但在第二个示例中成功?
这是一个功能齐全的示例页面,用于演示相关行为:
http://so-37035335.dev.zuma-design.com/
虽然上面的示例从 cdn 链接了 jQuery 2.2.3 版,但有问题的实际页面使用的是 1.7.1 版。 在两个版本中都可以观察到相同的行为。
更新:
在考虑了下面提供的一些评论和答案之后,我偶然发现了一些更令人困惑的东西。 用户@gdyrrahitis 提出了一个建议,导致我这样做:
function tbody_fade(row_id) {
$('tbody').load("load_tbody.php", function() {
$('tbody').fadeIn(0, function() {
$(this).find(row_id).addClass('green');
});
});
}
在fadeIn()
回调中添加类是有效的,即使持续时间为0 毫秒。 因此,这让我想知道......如果元素在理论上是有反正,做什么背景颜色的浏览器认为它之前,我补充一点类。 所以我记录了background-color
:
console.log($(row_id).css('background-color'));
你知道吗? 简单地获取背景颜色使一切正常:
function tbody_get_style(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).css('background-color');
$(row_id).addClass('green');
});
}
只需添加行$(row_id).css('background-color');
这似乎什么都不做,导致过渡效果起作用。 这是一个演示:
http://so-37035335-b.dev.zuma-design.com/
我只是傻眼了。 为什么这样做? 它只是增加了一个小的延迟,还是 jQuery 获取 css 属性以某种方式对新添加元素的状态产生了重大影响?
jQuery load
旨在将请求的所有内容放入页面。
您可以通过使用$.get
来利用 jQuery Deferred
对象的强大功能。
看看这个笨蛋。
来自 plunk 的代码片段
function load_tbody(row_id) {
$.when($.get("load_tbody.html", function(response) {
$('tbody').hide().html(response).fadeIn(100, function() {
$(this).find(row_id).addClass('green');
});
}));
}
我正在使用$.when
它将在$.get
解析后立即运行其回调,这意味着将获取 HTML 响应。 获取响应后,将其附加到tbody
,即淡入淡出( fadeIn
方法),并在显示后,将.green
类添加到所需的行。
请注意,如果您只是将 html 和类附加到row_id
,您将无法看到转换,因为它会立即执行。 使用fadeIn
一些漂亮的视觉技巧可以完成这项工作。
更新
在 DOM 中新添加的元素上,不会触发 CSS3 transition
。 这主要是因为控制所有动画的内部浏览器引擎。 有很多关于这个问题的解决方法的文章,以及 stackoverflow 的答案。 可以在那里找到其他资源,我相信这些资源可以比我更好地解释该主题。
这个答案是关于退后一步并更改在 DOM 中呈现动态元素的功能,而不打算使用setTimeout
或requestAnimationFrame
。 这只是另一种以清晰一致的方式实现您想要实现的目标的方法,因为 jQuery 可以跨浏览器工作。 fadeIn(100, ...
是赶上浏览器即将渲染的下一个可用帧所需要的。它可以少得多,值只是为了满足视觉美感。
另一种解决方法是根本不使用过渡,而是使用animation
。 但从我的测试来看,这在 IE Edge 中失败,在 Chrome、Firefox 上运行良好。
请查看以下资源:
更新 2
请看一下规范,因为那里有关于 CSS3 transitions
有趣内容。
...对一组同时发生的风格变化的处理称为风格变化事件。 (实现通常具有样式更改事件以对应于其所需的屏幕刷新率,以及何时需要依赖于它的脚本 API 的最新计算样式或布局信息。)
由于本规范没有定义何时发生样式更改事件,因此计算值的哪些更改被认为是同时发生的,作者应该意识到,在进行可能发生转换的更改后的一小段时间内更改任何转换属性可能会导致行为在实现之间有所不同,因为在某些实现中可能会认为更改是同时发生的,而在其他实现中则不会。
当样式更改事件发生时,实现必须根据在该事件中更改的计算值开始转换。 如果某个元素在该样式更改期间不在文档中,或者在前一个样式更改事件期间不在文档中,则不会在该样式更改事件中为该元素启动过渡。
添加元素时,需要回流。 这同样适用于添加类。 但是,当您在单个 javascript 回合中同时执行这两项操作时,浏览器会抓住机会优化第一个。 在这种情况下,只有一个(同时是初始和最终)样式值,所以不会发生转换。
setTimeout 技巧有效,因为它延迟了将类添加到另一轮 javascript 中,因此渲染引擎需要计算两个值,因为存在时间点,当第一个值呈现给用户时。
批处理规则还有另一个例外。 如果您试图访问它,浏览器需要计算立即值。 这些值之一是 offsetWidth。 当您访问它时,会触发回流。 另一个是在实际显示过程中单独完成的。 同样,我们有两个不同的样式值,因此我们可以及时插入它们。
这确实是极少数情况之一,当这种行为是可取的。 大多数情况下,在 DOM 修改之间访问导致回流的属性会导致严重的减速。
首选的解决方案可能因人而异,但对我来说,offsetWidth(或 getComputedStyle())的访问是最好的。 在某些情况下,当 setTimeout 被触发时,两者之间没有样式重新计算。 这种情况很少见,主要是在加载的网站上,但它会发生。 那么你将不会得到你的动画。 通过访问任何计算出的样式,您是在强制浏览器实际计算它
.css() 方法是从第一个匹配元素获取样式属性的便捷方法,特别是考虑到浏览器访问大多数这些属性的不同方式(基于标准的浏览器中的 getComputedStyle() 方法与 currentStyle 和 runtimeStyle Internet Explorer 中的属性)以及浏览器对某些属性使用的不同术语。
在某种程度上 .css() 相当于 jquery 的 javascript 函数getComputedStyle()
这解释了为什么在添加类之前添加 css 属性使一切正常
// Does not animate var $a = $('<div>') .addClass('box a') .appendTo('#wrapper'); $a.css('opacity'); $a.addClass('in'); // Check it's not just jQuery // does not animate var e = document.createElement('div'); e.className = 'box e'; document.getElementById('wrapper').appendChild(e); window.getComputedStyle(e).opacity; e.className += ' in';
.box { opacity: 0; -webkit-transition: all 2s; -moz-transition: all 2s; transition: all 2s; background-color: red; height: 100px; width: 100px; margin: 10px; } .box.in { opacity: 1; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <div id="wrapper"></div>
这是浏览器引起的常见问题。 基本上,当插入新元素时,它不会立即插入。 因此,当您添加该类时,它仍然不在 DOM 中,并且将在添加该类后计算它将如何呈现。 当元素被添加到 DOM 时,它已经有绿色背景,它从来没有白色背景,所以没有过渡要做。 有一些解决方法可以克服这里和那里的建议。 我建议你像这样使用requestAnimationFrame
:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
requestAnimationFrame(function() {
$(row_id).addClass('green');
});
});
}
编辑:
似乎上述解决方案不适用于所有情况。 我在这里发现了一个有趣的 hack ,它会在元素被真正解析并添加到 DOM 时触发一个事件。 如果在真正添加元素后更改背景颜色,则不会出现该问题。 小提琴
编辑:如果你想尝试这个解决方案(在 IE9 下不起作用):
包括这个 CSS:
@keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
@-moz-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
@-webkit-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
@-ms-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
@-o-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
.nodeInsertedTarget {
animation-duration: 0.01s;
-o-animation-duration: 0.01s;
-ms-animation-duration: 0.01s;
-moz-animation-duration: 0.01s;
-webkit-animation-duration: 0.01s;
animation-name: nodeInserted;
-o-animation-name: nodeInserted;
-ms-animation-name: nodeInserted;
-moz-animation-name: nodeInserted;
-webkit-animation-name: nodeInserted;
}
并使用这个 javascript:
nodeInsertedEvent= function(event){
event = event||window.event;
if (event.animationName == 'nodeInserted'){
var target = $(event.target);
target.addClass("green");
}
}
document.addEventListener('animationstart', nodeInsertedEvent, false);
document.addEventListener('MSAnimationStart', nodeInsertedEvent, false);
document.addEventListener('webkitAnimationStart', nodeInsertedEvent, false);
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).addClass('nodeInsertedTarget');
});
}
可以制成通用解决方案或库。 这只是一个快速的解决方案。
主要的解决方案是了解setTimeout(fn, 0)
及其用法。
这与 CSS 动画或 jQuery load
方法无关。 这是当您在 DOM 中执行多个任务时的情况。 并且延迟时间根本不重要,主要概念是使用setTimeout
。 有用的答案和教程:
为什么setTimeout(fn, 0)
有时有用? (堆栈溢出答案)
事件和计时深入( setTimeout(fn, 0)
真实例子)
JavaScript 计时器如何工作(由 jQuery Creator、John Resig 提供)
function insertNoDelay() { $('<tr><td>No Delay</td></tr>') .appendTo('tbody') .addClass('green'); } function insertWithDelay() { var $elem = $('<tr><td>With Delay</td></tr>') .appendTo('tbody'); setTimeout(function () { $elem.addClass('green'); }, 0); }
tr { transition: background-color 1000ms linear; } .green { background: green; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <table> <tbody> <tr><td>Hello World</td></tr> </tbody> </table> <button onclick="insertNoDelay()">No Delay</button> <button onclick="insertWithDelay()">With Delay</button>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.