在附加元素上触发 CSS 转换

IT技术 javascript css dom
2021-01-19 12:29:09

正如这个问题所观察到的,新附加元素上的即时 CSS 转换以某种方式被忽略 - 转换的结束状态立即呈现。

例如,给定这个 CSS(这里省略了前缀):

.box { 
  opacity: 0;
  transition: all 2s;
  background-color: red;
  height: 100px;
  width: 100px;
}

.box.in { opacity: 1; }

此元素的不透明度将立即设置为 1:

// Does not animate
var $a = $('<div>')
    .addClass('box a')
    .appendTo('#wrapper');
$a.addClass('in');

我已经看到了几种触发转换以获得预期行为的方法:

// Does animate
var $b = $('<div>')
    .addClass('box b')
    .appendTo('#wrapper');

setTimeout(function() {
    $('.b').addClass('in');
},0);

// Does animate
var $c = $('<div>')
    .addClass('box c')
    .appendTo('#wrapper');

$c[0]. offsetWidth = $c[0].offsetWidth
$c.addClass('in');

// Does animate
var $d = $('<div>')
    .addClass('box d')
    .appendTo('#wrapper');
$d.focus().addClass('in');

相同的方法适用于 vanilla JS DOM 操作 - 这不是特定于 jQuery 的行为。

编辑 - 我使用的是 Chrome 35。

JSFiddle(包括香草 JS 示例)。

  • 为什么附加元素上的直接 CSS 动画会被忽略?
  • 这些方法如何以及为什么起作用?
  • 有没有其他方法可以做到
  • 哪个(如果有)是首选解决方案?
6个回答

不为新添加的元素设置动画的原因是浏览器对回流进行批处理。

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

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

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

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

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

很好的答案,我希望我可以 +2
2021-03-17 12:29:09
@TJCrowder 看起来就像 Firefox 正在尝试进一步优化内容,如果在评估计时器回调之前没有绘制和回流,那么您就不走运了。这就是为什么最好知道如何同步触发它。如果您愿意,您可以在回调开始时进行。
2021-03-22 12:29:09
看起来调用getComputedStyle元素,即使将它分配给一个变量,也被 JIT 优化掉了。(只有当我将它登录到控制台时,回流似乎才会发生。offsetWidth尽管访问(即使没有使用)似乎也能工作。
2021-04-07 12:29:09
“setTimeout 技巧有效,因为它延迟了将类添加到另一轮 javascript 中”请注意,在 Firefox 上,setTimeout持续时间很重要。我不知道细节,但对我来说(Firefox 42,Linux),3ms 是最少的:jsfiddle.net/ca3ee5y8/4
2021-04-11 12:29:09
@Frizi 很好的解释。我很好奇,是否有一些更深入的官方文档?也许 MDN 上有什么?
2021-04-12 12:29:09

使用jQuery试试这个(这里有一个例子。):

var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.css('opacity'); // added
$a.addClass('in');

使用 Vanilla javaScript 试试这个:

var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e);
window.getComputedStyle(e).opacity; // added
e.className += ' in';

简要思路:

的getComputedStyle()刷新所有悬而未决的风格变化,迫使布局引擎来计算元素的当前状态,因此 的.css()的工作原理类似的方式。

关于css()来自jQuery网站:

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

您可以使用getComputedStyle()/css()代替setTimeout您也可以阅读本文以获取一些详细信息和示例。

另一个很好的答案,+1
2021-03-15 12:29:09
感谢您的竖起大拇指:-) @AndreaLigios
2021-03-22 12:29:09
我注意到 .css('opacity') 没有用 jquery 2.2 版刷新 IE11 中的样式更改。相反 .offset() 在 IE11 中起到了作用
2021-03-24 12:29:09

请使用下面的代码,使用“focus()”

查询

var $a = $('<div>')
    .addClass('box a')
    .appendTo('#wrapper');
$a.focus(); // focus Added
$a.addClass('in');

Javascript

var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e).focus(); // focus Added
e.className += ' in';
这个问题说了这么多。我想明白为什么
2021-04-03 12:29:09
这绝对是最好的方法。在 appendChild 之后添加一个“.focus()”是一个很好的优雅的解决方案。
2021-04-12 12:29:09

@Frizi 的解决方案有效,但有时我发现getComputedStyle当我更改元素的某些属性时它不起作用。如果这不起作用,您可以尝试getBoundingClientRect()如下,我发现它是防弹的:

假设我们有一个元素el,在这我们要转型opacity,但是eldisplay:none; opacity: 0

el.style.display = 'block';
el.style.transition = 'opacity .5s linear';

// reflow
el.getBoundingClientRect();

// it transitions!
el.style.opacity = 1;

我更喜欢 requestAnimationFrame + setTimeout(见这篇文章)。

const child = document.createElement("div");
child.style.backgroundColor = "blue";
child.style.width = "100px";
child.style.height = "100px";
child.style.transition = "1s";

parent.appendChild(child);

requestAnimationFrame(() =>
  setTimeout(() => {
    child.style.width = "200px";
  })
);

在这里试试

为什么你更喜欢在 appendChild 行的末尾添加“.focus()”?parent.appendChild(child).focus();
2021-04-12 12:29:09