触发附加元素的CSS转换

joe*_*ews 64 javascript css dom css3

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

例如,给定此CSS(此处省略前缀):

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

.box.in { opacity: 1; }
Run Code Online (Sandbox Code Playgroud)

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

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

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

// 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');
Run Code Online (Sandbox Code Playgroud)

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

编辑 - 我正在使用Chrome 35.

JSFiddle(包括vanilla JS示例).

  • 为什么忽略附加元素上的即时CSS动画?
  • 这些方法如何以及为何有效?
  • 还有其他方法可以做到这一点
  • 哪个(如果有的话)是首选解决方案?

Fri*_*izi 104

不动画新添加的元素的原因是浏览器批处理重排.

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

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

批处理规则还有另一个例外.如果您尝试访问它,浏览器需要计算立即值.其中一个值是offsetWidth.当您访问它时,将触发重排.另一个在实际显示期间单独完成.同样,我们有两个不同的样式值,因此我们可以及时插入它们.

当这种行为是可取的时,这实际上是极少数情况之一.大多数时候,在DOM修改之间访问引起回流的属性会导致严重的减速.

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

  • 很好的答案,我希望我能+2 (9认同)
  • *“setTimeout 技巧有效,因为它延迟了将类添加到另一个 javascript 回合”* 请注意,在 Firefox 上,`setTimeout` 持续时间很重要。我不知道细节,但对我来说(Firefox 42,Linux),3ms 是最少的:http://jsfiddle.net/ca3ee5y8/4/ (2认同)
  • 看起来在元素上调用 `getComputedStyle`,即使将它分配给一个变量,也会被 JIT 优化掉。(只有当我将它记录到控制台时,回流似乎才会发生。尽管访问 `offsetWidth`(即使没有使用)似乎也有效。 (2认同)

The*_*pha 28

使用jQuery试试这个(这里的例子):

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

使用Vanilla javaScript试试这个:

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

简要说明:

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

关于css()jQuery网站:

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

您可以使用getComputedStyle()/css()而不是setTimeout.您也可以阅读本文以获取一些详细信息和示例.


Man*_*nan 9

请使用以下代码,使用"focus()"

jQuery的

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

使用Javascript

var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e).focus(); // focus Added
e.className += ' in';
Run Code Online (Sandbox Code Playgroud)

  • 这个问题说的很多.我想了解_why_. (2认同)
  • 这绝对是最好的方法。在 appendChild 之后添加一个“.focus()”是一个不错的优雅解决方案。 (2认同)

nik*_*ong 6

@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;
Run Code Online (Sandbox Code Playgroud)


pom*_*ber 5

我更喜欢 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";
  })
);
Run Code Online (Sandbox Code Playgroud)

在这里试试。


Fra*_*cke 5

使用关键帧进行“创建动画”有什么根本性的错误吗?

(如果您绝对不想在初始节点上使用这些动画,请添加另一个类.initial抑制动画)

function addNode() {
  var node = document.createElement("div");
  var textnode = document.createTextNode("Hello");
  node.appendChild(textnode);

  document.getElementById("here").appendChild(node);  
}

setTimeout( addNode, 500);
setTimeout( addNode, 1000);
Run Code Online (Sandbox Code Playgroud)
body, html { background: #444; display: flex; min-height: 100vh; align-items: center; justify-content: center; }
button { font-size: 4em; border-radius: 20px; margin-left: 60px;}

div {
  width: 200px; height: 100px; border: 12px solid white; border-radius: 20px; margin: 10px;
  background: gray;
  animation: bouncy .5s linear forwards;
}

/* suppres for initial elements */
div.initial {
  animation: none;
}

@keyframes bouncy {
  0% { transform: scale(.1); opacity: 0 }  
  80% { transform: scale(1.15); opacity: 1 }  
  90% { transform: scale(.9); }  
  100% { transform: scale(1); }  
}
Run Code Online (Sandbox Code Playgroud)
<section id="here">
  <div class="target initial"></div>
</section>
Run Code Online (Sandbox Code Playgroud)