当this question观察到,新添加的元素上的立即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(包括vanilla JS示例)。
答案 0 :(得分:97)
不动画新添加元素的原因是浏览器批量重排。
添加元素时,需要重排。这同样适用于添加类。但是,当您在单个javascript回合中同时执行这两项操作时,浏览器会抓住机会优化第一个。在这种情况下,只有单个(初始和最终同时)样式值,因此不会发生转换。
setTimeout
技巧有效,因为它将类添加延迟到另一个javascript循环,因此渲染引擎有两个值,需要计算,因为有时间点,第一个一个呈现给用户。
批处理规则还有另一个例外。如果您尝试访问它,浏览器需要计算立即值。其中一个值为offsetWidth
。当您访问它时,将触发重排。另一个在实际显示期间单独完成。同样,我们有两个不同的样式值,因此我们可以及时插入它们。
这是非常少的场合之一,当这种行为是可取的时候。大多数时候,在DOM修改之间访问引起回流的属性会导致严重的减速。
首选解决方案可能因人而异,但对我而言,offsetWidth
(或getComputedStyle()
)的访问权限最佳。有些情况下,setTimeout
被触发时,两者之间没有样式重新计算。这是罕见的情况,主要是在加载的网站上,但它发生了。那你就不会得到你的动画了。通过访问任何计算的样式,您强制浏览器实际计算它。
答案 1 :(得分:27)
使用jQuery
试试这个(An Example Here.):
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和。{ Internet Explorer中的runtimeStyle属性)和不同的术语 浏览器用于某些属性。
您可以使用getComputedStyle()/css()
代替setTimeout
。您也可以阅读this article了解一些详细信息和示例。
答案 2 :(得分:6)
@Frizi的解决方案有效,但有时我发现当我更改某个元素的某些属性时getComputedStyle
无效。如果这不起作用,您可以按照以下方式尝试getBoundingClientRect()
,我发现这是防弹的:
假设我们有一个元素el
,我们想要转换opacity
,但el
是display:none; opacity: 0
:
el.style.display = 'block';
el.style.transition = 'opacity .5s linear';
// reflow
el.getBoundingClientRect();
// it transitions!
el.style.opacity = 1;
答案 3 :(得分:5)
请使用以下代码,使用“focus()”
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.focus(); // focus Added
$a.addClass('in');
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e).focus(); // focus Added
e.className += ' in';
答案 4 :(得分:2)
我尝试使用requestAnimationFrame()
来允许浏览器在其下一个可用框架上进行绘制,而不是试图强制立即进行重绘或样式计算。
在Chrome + Firefox中,浏览器会过度优化渲染,因此这仍然无法提供帮助(适用于Safari)。
我决定用setTimeout()
手动强制延迟,然后使用requestAnimationFrame()
负责让浏览器绘画。如果在超时结束之前没有绘制追加,则可能会忽略动画,但它似乎可靠地工作。
setTimeout(function () {
requestAnimationFrame(function () {
// trigger the animation
});
}, 20);
我选择了20ms,因为它在60fps(16.7ms)时大于1帧,有些浏览器不会注册超时<5ms。
手指交叉,应该强制动画开始进入下一帧,然后在浏览器准备再次绘制时负责任地启动它。
答案 5 :(得分:2)
setTimeout()
仅由于比赛条件而起作用,应改为使用requestAnimationFrame()
。但是offsetWidth
技巧在所有选项中效果最好。
这是一个示例情况。我们有一系列的框,每个框需要按顺序向下动画。为了使所有功能正常工作,我们每个元素需要获得两次动画帧,这里我在动画之前放置了一次,在动画之后放置了一次,但是如果将它们一个接一个放置,这似乎也是可行的。< / p>
requestAnimationFrame
:无论2 getFrame()
和单个set-class-name步骤的排序顺序如何,都可以使用。
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
await getFrame();
//await delay(1);
box.className = 'move';
// AFTER
await getFrame();
//await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
setTimeout
失败:由于这是基于比赛条件的,因此确切的结果会因浏览器和计算机的不同而有很大差异。增加setTimeout
延迟有助于动画更频繁地赢得比赛,但不能保证任何效果。
在我的Surfacebook 1上使用Firefox,并且延迟2ms / el,我发现大约50%的盒子失败了。以20ms / el的延迟,我看到大约10%的盒子失败了。
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
//await getFrame();
await delay(1);
box.className = 'move';
// AFTER
//await getFrame();
await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
requestAnimationFrame
,然后setTimeout
通常有效:这是Brendan's solution(首先是setTimeout
或pomber's solution(首先是requestAnimationFrame
)。
# works:
getFrame()
delay(0)
ANIMATE
# works:
delay(0)
getFrame()
ANIMATE
# works:
delay(0)
ANIMATE
getFrame()
# fails:
getFrame()
ANIMATE
delay(0)
(对我来说)它不起作用的一种情况是获取帧,然后进行动画处理,然后进行延迟。我没有解释原因。
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
await getFrame();
await delay(1);
box.className = 'move';
// AFTER
//await getFrame();
//await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
答案 6 :(得分:0)
与Brendan不同,我发现requestAnimationFrame()
适用于Chrome 63,Firefox 57,IE11和Edge。
var div = document.createElement("div");
document.body.appendChild(div);
requestAnimationFrame(function () {
div.className = "fade";
});
&#13;
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
&#13;
答案 7 :(得分:0)
答案 8 :(得分:0)
(如果您严格不希望在初始节点上使用这些动画,请添加另一个.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);
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); }
}
<section id="here">
<div class="target initial"></div>
</section>
答案 9 :(得分:0)
要继续代码行流,只需在函数声明前添加 async
并添加
await new Promise(resolve=>{setTimeout(resolve, 10)})
在附加孩子之后。它将暂停 10ms
的代码执行,然后其余代码按原样正常执行,无需修改任何内容或包装到超时函数。