我有一个函数,它使用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属性以某种方式对新添加的元素的状态产生实质性影响?
答案 0 :(得分:5)
jQuery load
旨在删除页面中请求的所有内容。
您可以使用Deferred
来充分利用jQuery $.get
对象的强大功能。
看看这个plunk。
来自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
将在tbody
解析后立即运行,这意味着将获取HTML响应。获取响应后,它将附加到fadeIn
,这是fadedIn(.green
方法),在显示之后,row_id
类将添加到所需的行。
请注意,如果您继续将html和类附加到fadeIn
,您将无法看到转换,因为它会立即执行。 <{1}}的一个不错的视觉技巧可以完成这项工作。
<强>更新强>
在新添加到DOM的元素上,不会触发CSS3 transition
。这主要是因为内部浏览器引擎控制所有动画。有很多关于该问题的解决方法的文章,以及stackoverflow答案。可以在那里找到其他资源,我相信这可以比我更好地解释这个话题。
这个答案是关于退一步并改变在DOM中呈现动态元素的功能,而不使用setTimeout
或requestAnimationFrame
。这是通过jQuery在浏览器中运行时以清晰一致的方式实现您想要实现的目标的另一种方式。 fadeIn(100, ...
是了解浏览器即将呈现的下一个可用帧所需要的。它可能更少,价值只是为了满足视觉美学。
另一种解决方法是根本不使用转换,而是使用animation
。但是根据我的测试,这在IE Edge中失败,在Chrome,Firefox上运行良好。
请查看以下资源:
更新2
请看一下specification,因为有关CSS3 transitions
的有趣内容。
...对一组同时样式更改的处理称为样式更改事件。 (实现通常具有样式更改事件以与其所需的屏幕刷新率相对应,并且当依赖于它的脚本API需要最新的计算样式或布局信息时。)
由于此规范未定义样式更改事件何时发生,因此对计算值的哪些更改被同时考虑,因此作者应注意在进行可能转换的更改后,在很短的时间内更改任何转换属性可能会导致实现之间的行为不同,因为在某些实现中可能会同时考虑更改,但其他实现不会。
当发生样式更改事件时,实现必须根据在该事件中更改的计算值开始转换。如果在该样式更改期间元素不在文档中,或者在上一个样式更改事件期间不在文档中,则不会在该样式更改事件中为该元素启动过渡。
答案 1 :(得分:3)
添加元素时,需要重排。这同样适用于添加类。但是当你在单个javascript循环中同时执行这两个操作时,浏览器会有机会优化第一个。在这种情况下,只有单个(初始和最终同时)样式值,因此不会发生转换。
setTimeout技巧有效,因为它将类添加延迟到另一个javascript循环,因此渲染引擎存在两个值,需要计算,因为有时间点,当第一个呈现给用户。
批处理规则还有另一个例外。如果您尝试访问它,浏览器需要计算立即值。其中一个值是offsetWidth。当您访问它时,将触发重排。另一个在实际显示期间单独完成。同样,我们有两个不同的样式值,因此我们可以及时插入它们。
这是非常少的场合之一,当这种行为是可取的时候。大多数时候,在DOM修改之间访问引起回流的属性会导致严重的减速。
首选解决方案可能因人而异,但对我来说,offsetWidth(或getComputedStyle())的访问权限是最佳的。有些情况下,当setTimeout被触发时,两者之间没有样式重新计算。这是罕见的情况,主要是在加载的网站上,但它发生了。那你就不会得到你的动画了。通过访问任何计算的样式,您强制浏览器实际计算它
.css()方法是从第一个匹配元素获取样式属性的便捷方法,特别是考虑到浏览器访问大多数这些属性的不同方式(基于标准的浏览器中的getComputedStyle()方法与Internet Explorer中的currentStyle和runtimeStyle属性)以及浏览器用于某些属性的不同术语。
在某种程度上.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>
答案 2 :(得分:1)
这是浏览器引起的常见问题。基本上,当插入新元素时,不会立即插入。因此,当您添加类时,它仍然不在DOM中,并且在添加类之后计算它的呈现方式。当元素添加到DOM时,它已经具有绿色背景,它从不具有白色背景,因此没有转换要做。根据建议here和there,有一些解决方法可以解决此问题。我建议你像这样使用requestAnimationFrame
:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
requestAnimationFrame(function() {
$(row_id).addClass('green');
});
});
}
修改强>
似乎上述解决方案并不适用于所有情况。我找到了一个有趣的hack here,当一个元素被真正解析并添加到DOM时会触发一个事件。如果在真正添加元素后更改背景颜色,则不会出现问题。 Fiddle
编辑:如果你想尝试这个解决方案(在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');
});
}
可以制作通用解决方案或库。这只是一个快速的解决方案。
答案 3 :(得分:1)
主要解决方案是了解setTimeout(fn, 0)
及其用法。
这与CSS动画或jQuery load
方法无关。
当您在DOM中执行多个任务时,就会出现这种情况。
延迟时间根本不重要,主要概念是使用setTimeout
。
有用的答案和教程:
Why is setTimeout(fn, 0)
sometimes useful?(stackoverflow回答)
Events and timing in-depth(setTimeout(fn, 0)
的现实例子)
How JavaScript Timers Work(由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>