【问题标题】:CSS Transition fails on jQuery .load callbackjQuery .load 回调上的 CSS 转换失败
【发布时间】:2016-08-30 08:10:16
【问题描述】:

我有一个函数,它使用 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);
    });
}

jQuery docs for .load() 状态:

如果提供了“完成”回调,它会在之后执行 已执行后处理和 HTML 插入。

对我来说,这表明新元素已经加载到 dom 中并应用了现有样式,并准备好进行操作。为什么转换在第一个示例中失败,而在第二个示例中成功?

这是一个功能齐全的示例页面,用于演示相关行为:

http://so-37035335.dev.zuma-design.com/

虽然上面的示例从 cdn 链接 jQuery 版本 2.2.3,但实际页面使用的是 1.7.1 版本。在两个版本中都可以观察到相同的行为。

更新:

在考虑了下面提供的一些 cmets 和答案后,我偶然发现了一些更令人困惑的东西。用户@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 文档上阅读了一些与类似内容相关的内容。文档建议用 0 ms 包装你对 settimeout 的调用。试试看,它的运行延迟是 0 毫秒吗?如果是这样,那么只需 google jquery docs for settimeout。有一个解释。
  • @Uzbekjon - 0ms 不起作用。刚刚尝试了一些测试,Chrome 似乎可以使用 2ms,firefox 需要 5ms。
  • 我建议你使用requestAnimationFrame 而不是setTimeout。您可以为旧版浏览器填充 polyfill。 developer.mozilla.org/en-US/docs/Web/API/window/…
  • @GökhanKurt - 我在这里没有使用 setTimeout 作为任何类型的解决方案 - 只是为了演示这个问题。
  • @billynoah 这就是我将其写为评论的原因。如果我的意思是回答你的问题,我会把它写成答案。

标签: javascript jquery html css


【解决方案1】:

jQuery load 旨在将请求的所有内容放入页面中。

您可以改用$.get 来利用 jQuery Deferred 对象的强大功能。

看看这个plunk

来自 plunk 的代码 sn-p

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(fadeIn 方法)并在显示后,将 .green 类添加到所需的行。

请注意,如果您只是将 html 和类附加到 row_id,您将无法看到转换,因为它会立即执行。使用fadeIn 进行一些漂亮的视觉技巧就可以完成这项工作。

更新

在新添加到 DOM 的元素上,不会触发 CSS3 transition。这主要是因为控制所有动画的内部浏览器引擎。有很多关于这个问题的解决方法的文章,以及 stackoverflow 的答案。可以在那里找到其他资源,我相信它们可以比我更好地解释这个主题。

这个答案是关于退后一步并更改在 DOM 中呈现动态元素的功能,而不使用 setTimeoutrequestAnimationFrame。这只是以清晰一致的方式实现您想要实现的目标的另一种方式,因为 jQuery 可以跨浏览器工作。 fadeIn(100, ... 是赶上浏览器即将呈现的下一个可用帧所需要的。可以少很多,只是为了满足视觉审美。

另一种解决方法是根本不使用转换,而是使用animation。但根据我的测试,这在 IE Edge 中失败,在 Chrome、Firefox 上运行良好。

请查看以下资源:

更新 2

请看一下specification,因为那里有关于 CSS3 的有趣内容transitions

...对一组同时发生的样式更改的处理称为样式更改事件。 (实现通常有一个样式更改事件来对应其所需的屏幕刷新率,并且当依赖于它的脚本 API 需要最新的计算样式或布局信息时。)

由于本规范没有定义样式更改事件何时发生,因此计算值的哪些更改被认为是同时发生的,因此作者应该注意,在进行可能转换的更改后的一小段时间内更改任何转换属性可能会导致不同实现之间的行为发生变化,因为在某些实现中可能会认为这些更改是同时发生的,而在其他实现中则不会。

当样式更改事件发生时,实现必须根据在该事件中更改的计算值启动转换。如果某个元素在该样式更改期间不在文档中,或者在上一次样式更改事件期间不在文档中,则不会在该样式更改事件中为该元素启动转换。

【讨论】:

  • 这并没有真正回答我问它的上下文中的问题,使用 ajax 请求和回调。如果回调触发按照文档中的说明插入元素后,为什么我需要延迟?
  • 因为无法触发过渡。将更新答案
  • 谢谢 - 我实际上将您的功能解释添加到另一个演示页面,即使 fadeIn() 设置为 0 也可以。我还是不太明白为什么。 fadeIn() 是否像 dom 一样准备好添加元素?
  • 很高兴它对您有用。实际上不,它与 DOM 没有完全关系,而是与处理浏览器动画(CSS 过渡、CSS 动画、SMIL 等)的内部引擎有关。
  • 只是为了让你知道我完全不鼓励使用.load() 方法,因为它在过去给我带来了很多类似情况的麻烦。最好坚持$.get 和承诺。
【解决方案2】:

添加元素时,需要回流。这同样适用于添加类。但是,当您在单个 javascript 回合中同时执行这两项操作时,浏览器会利用机会优化第一个。在这种情况下,只有一个(同时是初始和最终)样式值,因此不会发生转换。

setTimeout 技巧有效,因为它将类添加延迟到另一轮 javascript,因此呈现引擎存在两个值,需要计算,因为存在时间点,当第一个呈现给用户。

批处理规则还有另一个例外。如果您尝试访问它,浏览器需要计算立即值。这些值之一是 offsetWidth。当您访问它时,会触发回流。另一个是在实际显示期间单独完成的。同样,我们有两个不同的样式值,因此我们可以及时对它们进行插值。

这确实是极少数情况之一,当这种行为是可取的。大多数情况下,在 DOM 修改之间访问导致回流的属性会导致严重的减速。

首选的解决方案可能因人而异,但对我来说,offsetWidth(或getComputedStyle())的访问是最好的。在某些情况下,当 setTimeout 被触发而没有在两者之间重新计算样式时。这是罕见的情况,主要是在加载的网站上,但它会发生。然后你不会得到你的动画。通过访问任何计算过的样式,您会强制浏览器实际计算它

Trigger CSS transition on appended element

最后一部分的解释

.css() 方法是从第一个匹配元素获取样式属性的便捷方法,尤其是考虑到浏览器访问大多数这些属性的不同方式(基于标准的浏览器中的 getComputedStyle() 方法与Internet Explorer 中的 currentStyle 和 runtimeStyle 属性)以及浏览器对某些属性使用的不同术语。

在某种程度上 .css() 相当于 jquery 的 javascript 函数 getComputedStyle(),这解释了为什么在添加类之前添加 css 属性会使一切正常

Jquery .css() documentation

// 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>

Here is the listed work arounds [SO Question]

css transitions on new elements [SO Question]

【讨论】:

  • 谢谢。这是非常透彻的,以我可以理解的方式解释事物并提供了一些很好的参考材料。
  • @billynoah 很乐意帮助比利诺亚
【解决方案3】:

主要的解决方案是了解setTimeout(fn, 0)及其用法。

这与 CSS 动画或 jQuery load 方法无关。 这是您在 DOM 中执行多项任务时的情况。 而且延迟时间根本不重要,主要概念是使用setTimeout。 有用的答案和教程:

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>

【讨论】:

  • 在您的示例中,我没有看到任何一个按钮的 CSS 过渡。如果我编辑你的 sn-p 并增加超时,我会在 7 毫秒开始看到过渡效果(使用 Firefox v45)。所以说“延迟时间根本不重要”是不正确的。
【解决方案4】:

这是由浏览器引起的常见问题。基本上,当插入新元素时,它不会立即插入。因此,当您添加类时,它仍然不在 DOM 中,并且将在添加类之后计算它的渲染方式。当元素被添加到 DOM 时,它已经有绿色背景,它从来没有白色背景,所以没有过渡。正如herethere 所建议的,有一些解决方法可以克服这个问题。我建议你像这样使用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');
    });
}

可以制成通用解决方案或库。这只是一个快速的解决方案。

【讨论】:

  • 我对此很感兴趣,但不幸的是它不起作用。我已更新示例页面 (so-37035335.dev.zuma-design.com) 以包含您的功能并且没有过渡 - 在 Chrome 和 Firefox 中尝试过。
  • 试过你的链接。你说的对。适用于 Chrome、IE Edge、IE 11 但不适用于 Firefox。我不知道为什么它在 Chrome 中不适合你。也许是关于计算机规格。
  • 可能是..这是一件奇怪的事情。我认为如果在将某些内容添加到 DOM 之后触发事件,那么 css 转换应该可以正常工作,而不会出现任何类型的时间延迟黑客。
  • 这可能是另一种解决方法,但我不确定它如何回答这个问题。我不是在这里寻找更多的解决方法 - 如图所示,我已经有了一个使用 setTimeout 的简单、有效且廉价的解决方法。
  • @billynoah 这是一种解决方法,但它解释了您的问题的原因。基本上,当您添加“绿色”类时,该元素尚未在 DOM 中解析。过渡适用于背景颜色的变化。但是元素的背景颜色不会改变,因为在它“真正”添加到 DOM 之前它是绿色的。但是,如果您延迟执行此操作,则背景一开始是白色的,然后在添加类后变为绿色。此外,如果元素更难解析,10 毫秒的延迟可能还不够,因此它不是确定性解决方案。
猜你喜欢
  • 2013-12-07
  • 2018-02-09
  • 1970-01-01
  • 2014-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多