Trigger CSS transition on appended element - javascript

As this question observes, immediate CSS transitions on newly-appended elements are somehow ignored - the end state of the transition is rendered immediately.
For example, given this CSS (prefixes omitted here):
.box {
opacity: 0;
transition: all 2s;
background-color: red;
height: 100px;
width: 100px;
}
.box.in { opacity: 1; }
The opacity of this element will be set immediately to 1:
// Does not animate
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.addClass('in');
I have seen several ways of triggering the transition to get the expected behaviour:
// 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');
The same methods apply to vanilla JS DOM manipulation - this is not jQuery-specific behaviour.
Edit - I am using Chrome 35.
JSFiddle (includes vanilla JS example).
Why are immediate CSS animations on appended elements ignored?
How and why do these methods work?
Are there other ways of doing it
Which, if any, is the preferred solution?

The cause of not animating the newly added element is batching reflows by browsers.
When element is added, reflow is needed. The same applies to adding the class. However when you do both in single javascript round, browser takes its chance to optimize out the first one. In that case, there is only single (initial and final at the same time) style value, so no transition is going to happen.
The setTimeout trick works, because it delays the class addition to another javascript round, so there are two values present to the rendering engine, that needs to be calculated, as there is point in time, when the first one is presented to the user.
There is another exception of the batching rule. Browser need to calculate the immediate value, if you are trying to access it. One of these values is offsetWidth. When you are accessing it, the reflow is triggered. Another one is done separately during the actual display. Again, we have two different style values, so we can interpolate them in time.
This is really one of very few occasion, when this behaviour is desirable. Most of the time accessing the reflow-causing properties in between DOM modifications can cause serious slowdown.
The preferred solution may vary from person to person, but for me, the access of offsetWidth (or getComputedStyle()) is the best. There are cases, when setTimeout is fired without styles recalculation in between. This is rare case, mostly on loaded sites, but it happens. Then you won't get your animation. By accessing any calculated style, you are forcing the browser to actually calculate it.

Using jQuery try this (An Example Here.):
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.css('opacity'); // added
$a.addClass('in');
Using Vanilla javaScript try this:
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e);
window.getComputedStyle(e).opacity; // added
e.className += ' in';
Brief idea:
The getComputedStyle() flushes all pending style changes and
forces the layout engine to compute the element's current state, hence
.css() works similar way.
About css()from jQuery site:
The .css() method is a convenient way to get a style property from the
first matched element, especially in light of the different ways
browsers access most of those properties (the getComputedStyle()
method in standards-based browsers versus the currentStyle and
runtimeStyle properties in Internet Explorer) and the different terms
browsers use for certain properties.
You may use getComputedStyle()/css() instead of setTimeout. Also you may read this article for some details information and examples.

Please use the below code, use "focus()"
Jquery
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';

I prefer requestAnimationFrame + setTimeout (see this post).
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";
})
);
Try it here.

#Frizi's solution works, but at times I've found that getComputedStyle has not worked when I change certain properties on an element. If that doesn't work, you can try getBoundingClientRect() as follows, which I've found to be bulletproof:
Let's assume we have an element el, on which we want to transition opacity, but el is display:none; opacity: 0:
el.style.display = 'block';
el.style.transition = 'opacity .5s linear';
// reflow
el.getBoundingClientRect();
// it transitions!
el.style.opacity = 1;

Anything fundamentally wrong with using keyframes for "animate on create"?
(if you strictly don't want those animations on the initial nodes, add another class .initial inhibitin animation)
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>

Rather than trying to force an immediate repaint or style calculation, I tried using requestAnimationFrame() to allow the browser to paint on its next available frame.
In Chrome + Firefox, the browser optimizes rendering too much so this still doesn't help (works in Safari).
I settled on manually forcing a delay with setTimeout() then using requestAnimationFrame() to responsibly let the browser paint. If the append hasn't painted before the timeout ends the animation might be ignored, but it seems to work reliably.
setTimeout(function () {
requestAnimationFrame(function () {
// trigger the animation
});
}, 20);
I chose 20ms because it's larger than 1 frame at 60fps (16.7ms) and some browsers won't register timeouts <5ms.
Fingers crossed that should force the animation start into the next frame and then start it responsibly when the browser is ready to paint again.

setTimeout() works only due to race conditions, requestAnimationFrame() should be used instead. But the offsetWidth trick works the best out of all options.
Here is an example situation. We have a series of boxes that each need to be animated downward in sequence. To get everything to work we need to get an animation frame twice per element, here I put once before the animation and once after, but it also seems to work if you just put them one after another.
Using requestAnimationFrame twice works:
Works regardless of how exactly the 2 getFrame()s and single set-class-name step are ordered.
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);
}
Using setTimeout twice fails:
Since this is race condition-based, exact results will vary a lot depending on your browser and computer. Increasing the setTimeout delay helps the animation win the race more often, but guarantees nothing.
With Firefox on my Surfacebook 1, and with a delay of 2ms / el, I see about 50% of the boxes failing. With a delay of 20ms / el I see about 10% of the boxes failing.
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);
}
Using requestAnimationFrame once and setTimeout usually works:
This is Brendan's solution (setTimeout first) or pomber's solution (requestAnimationFrame first).
# works:
getFrame()
delay(0)
ANIMATE
# works:
delay(0)
getFrame()
ANIMATE
# works:
delay(0)
ANIMATE
getFrame()
# fails:
getFrame()
ANIMATE
delay(0)
The once case where it doesn't work (for me) is when getting a frame, then animating, then delaying. I do not have an explanation why.
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);
}

Edit: the technique used in the original answer, below the horizontal rule, does not work 100% of the time, as noted in the comments by mindplay.dk.
Currently, if using requestAnimationFrame(), pomber's approach is probably the best, as can be seen in the article linked to in pomber's answer. The article has been updated since pomber answered, and it now mentions requestPostAnimationFrame(), available behind the Chrome flag --enable-experimental-web-platform-features now.
When requestPostAnimationFrame() reaches a stable state in all major browsers, this will presumably work reliably:
const div = document.createElement("div");
document.body.appendChild(div);
requestPostAnimationFrame(() => div.className = "fade");
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
For the time being, however, there is a polyfill called AfterFrame, which is also referenced in the aforementioned article. Example:
const div = document.createElement("div");
document.body.appendChild(div);
window.afterFrame(() => div.className = "fade");
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
<script src="https://unpkg.com/afterframe/dist/afterframe.umd.js"></script>
Original answer:
Unlike Brendan, I found that requestAnimationFrame() worked in Chrome 63, Firefox 57, IE11 and Edge.
var div = document.createElement("div");
document.body.appendChild(div);
requestAnimationFrame(function () {
div.className = "fade";
});
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}

Related

Why does setting CSS property using Promise.then not actually happen at the then block?

Please try and run the following snippet, then click on the box.
const box = document.querySelector('.box')
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)'
new Promise(resolve => {
setTimeout(() => {
box.style.transition = 'none'
box.style.transform = ''
resolve('Transition complete')
}, 2000)
}).then(() => {
box.style.transition = ''
})
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class = "box"></div>
What I expect to happen:
Click happens
Box starts translating horizontally by 100px (this action takes two seconds)
On click, a new Promise is also created. Inside said Promise, a setTimeout function is set to 2 seconds
After the action is completed (two seconds have elapsed), setTimeout runs its callback function and set transition to none. After doing that, setTimeout also reverts transform to its original value, thus rendering the box to appear at the original location.
The box appears at the original location with no transition effect problem here
After all of those finish, set the transition value of the box back to its original value
However, as can be seen, the transition value does not seem to be none when running. I know that there are other methods to achieve the above, e.g. using keyframe and transitionend, but why does this happen? I explicitly set the transition back to its original value only after the setTimeout finishes its callback, thus resolving the Promise.
EDIT
As per request, here's a gif of the code displaying the problematic behaviour:
The event loop batches style changes. If you change the style of an element on one line, the browser doesn't show that change immediately; it'll wait until the next animation frame. This is why, for example
elm.style.width = '10px';
elm.style.width = '100px';
doesn't result in flickering; the browser only cares about the style values set after all Javascript has completed.
Rendering occurs after all Javascript has completed, including microtasks. The .then of a Promise occurs in a microtask (which will effectively run as soon as all other Javascript has finished, but before anything else - such as rendering - has had a chance to run).
What you're doing is you're setting the transition property to '' in the microtask, before the browser has started rendering the change caused by style.transform = ''.
If you reset the transition to the empty string after a requestAnimationFrame (which will run just before the next repaint), and then after a setTimeout (which will run just after the next repaint), it'll work as expected:
const box = document.querySelector('.box')
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)'
setTimeout(() => {
box.style.transition = 'none'
box.style.transform = ''
// resolve('Transition complete')
requestAnimationFrame(() => {
setTimeout(() => {
box.style.transition = ''
});
});
}, 2000)
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class="box"></div>
You are facing a variation of the transition doesn't work if element start hidden problem, but directly on the transition property.
You can refer to this answer to understand how the CSSOM and the DOM are linked for the "redraw" process.
Basically, browsers will generally wait until the next painting frame to recalculate all the new box positions and thus to apply CSS rules to the CSSOM.
So in your Promise handler, when you reset the transition to "", the transform: "" has still not been calculated yet. When it will get calculated, the transition will already have been reset to "" and the CSSOM will trigger the transition for the transform update.
However, we can force the browser to trigger a "reflow" and thus we can make it recalculate the position of your element, before we reset the transition to "".
const box = document.querySelector('.box')
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)'
new Promise(resolve => {
setTimeout(() => {
box.style.transition = 'none'
box.style.transform = ''
box.offsetWidth; // this triggers a reflow
resolve('Transition complete')
}, 2000)
}).then(() => {
box.style.transition = ''
})
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class = "box"></div>
Which makes the use of the Promise quite unnecessary:
const box = document.querySelector('.box')
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)'
setTimeout(() => {
box.style.transition = 'none'
box.style.transform = ''
box.offsetWidth; // this triggers a reflow
// even synchronously
box.style.transition = ''
}, 2000)
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class = "box"></div>
And for an explanation on micro-tasks, like Promise.resolve() or MutationEvents, or queueMicrotask(), you need to understand they'll get ran as soon as the current task is done, 7th step of the Event-loop processing model, before the rendering steps.
So in your case, it's very like if it were ran synchronously.
By the way, beware micro-tasks can be as blocking as a while loop:
// this will freeze your page just like a while(1) loop
const makeProm = ()=> Promise.resolve().then( makeProm );
I believe your issue is just that in your .then you are setting the transition to '', when you should be setting it to none as you did in the timer callback.
const box = document.querySelector('.box');
box.addEventListener('click', e => {
if (!box.style.transform) {
box.style.transform = 'translateX(100px)';
new Promise(resolve => {
setTimeout(() => {
box.style.transition = 'none';
box.style.transform = '';
resolve('Transition complete');
}, 2000)
}).then(() => {
box.style.transition = 'none'; // <<----
})
}
})
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
transition: all 2s ease;
}
<div class = "box"></div>
I appreciate this isn't quite what you're looking for, but - out of curiosity and for the sake of completeness - I wanted to see if I could write a CSS-only approach to this effect.
Almost... but it turns out I still had to include a single line of javascript.
Working Example:
document.querySelector('.box').addEventListener('animationend', (e) => e.target.blur());
.box {
width: 100px;
height: 100px;
border-radius: 5px;
background-color: #121212;
cursor: pointer;
}
.box:focus {
animation: boxAnimation 2s ease;
}
#keyframes boxAnimation {
100% {transform: translateX(100px);}
}
<div class="box" tabindex="0"></div>

Changing an HTML element's style in JavaScript with its CSS transition temporarily disabled isn't reliably functioning [duplicate]

This question already has answers here:
How can I force WebKit to redraw/repaint to propagate style changes?
(33 answers)
Closed 4 years ago.
Currently I am working on an animation for a website which involves two elements having their position changed over a period of time and usually reset to their initial position. Only one element will be visible at a time and everything ought to run as smoothly as possible.
Before you ask, a CSS-only solution is not possible as it is dynamically generated and must be synchronised. For the sake of this question, I will be using a very simplified version which simply consists of a box moving to the right. I shall be referring only to this latter example unless explicitly stated for the remainder of this question to keep things simple.
Anyway, the movement is handled by the CSS transition property being set so that the browser can do the heavy lifting for that. This transition must then be done away with in order to reset the element's position in an instant. The obvious way of doing so would be to do just that then reapply transition when it needs to get moving again, which is also right away. However, this isn't working. Not quite. I'll explain.
Take a look at the JavaScript at the end of this question or in the linked JSFiddle and you can see that is what I'm doing, but setTimeout is adding a delay of 25ms in between. The reason for this is (and it's probably best you try this yourself) if there is either no delay (which is what I want) or a very short delay, the element will either intermittently or continually stay in place, which isn't the desired effect. The higher the delay, the more likely it is to work, although in my actual animation this causes a minor jitter because the animation works in two parts and is not designed to have a delay.
This does seem like the sort of thing that could be a browser bug but I've tested this on Chrome, Firefox 52 and the current version of Firefox, all with similar results. I'm not sure where to go from here as I have been unable to find this issue reported anywhere or any solutions/workarounds. It would be much appreciated if someone could find a way to get this reliably working as intended. :)
Here is the JSFiddle page with an example of what I mean.
The markup and code is also pasted here:
var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;
setInterval(function() {
box.style.transition = "none";
box.style.left = "1em";
setTimeout(function() {
box.style.transition = "1s linear";
box.style.left = "11em";
}, delay);
}, 1000);
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Force the DOM to recalculate itself before setting a new transition after reset. This can be achieved for example by reading the offset of the box, something like this:
var box = document.getElementById("box");
setInterval(function(){
box.style.transition = "none";
box.style.left = "1em";
let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
box.style.transition = "1s linear";
box.style.left = "11em";
}, 1000);
body {
background-color: rgba(0,0,0,0);
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
See also a working demo at jsFiddle.
Normally the DOM is not updated when you set its properties until the script will be finished. Then the DOM is recalculated and rendered. However, if you read a DOM property after changing it, it forces a recalculation immediately.
What happens without the timeout (and property reading) is, that the style.left value is first changed to 1em, and then immediately to 11em. Transition takes place after the script will be fihished, and sees the last set value (11em). But if you read a position value between the changes, transition has a fresh value to go with.
Instead of making the transition behave as an animation, use animation, it will do a much better job, most importantly performance-wise and one don't need a timer to watch it.
With the animation events one can synchronize the animation any way suited, including fire of a timer to restart or alter it.
Either with some parts being setup with CSS
var box = document.getElementById("box");
box.style.left = "11em"; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = "1em";
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
animation: move_me 1s linear 4;
}
#keyframes move_me {
0% { left: 1em; }
}
<div id="box"></div>
Or completely script based
var prop = 'left', value1 = '1em', value2 = '11em';
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '#keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);
var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = value1;
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>

Does a hidden/transparent element impact rendering performance?

So I have a Polymer app that I am writing. I have written a non-polymer web-component for a loading overlay that I can show whilst Polymer is loading and when the app Websocket is connecting/reconnecting.
Here is an exert of some of the CSS I have to give an indication of what I am doing:
.overlay {
background: #000;
bottom: 0;
height: 100%;
left: 0;
opacity: 0;
pointer-events: none;
position: fixed;
right: 0;
transition: opacity 0.2s;
top: 0;
width: 100%;
z-index: 9999999;
}
.overlay[opened] {
opacity: 0.8;
pointer-events: auto;
}
.loader {
display: none;
}
.overlay[opened] .loader {
display: block;
}
Now this overlay and the CSS based loader animation I have is only used when I load the application realistically, however if the WebSocket were to disconnect it would be shown too.
My question is, for performance reasons, should I be removing the element from the DOM entirely and add it back if its required? Does the fact that the overlay is completely transparent when not in use and the loader animation is hidden mean they have no impact on drawing performance?
Note: I am looking to avoid the "don't micro-optimise" answer if possible ;)
TL;DR:
In general, a rendered element affects page performance when changes to it trigger repaint on subsequent elements in DOM or when it triggers resize on its parent(s), as resize can get expensive from being fired up to 100 times/second, depending on device.
As long as changes to your element do not trigger repaint on subsequent elements in DOM tree, the difference between having it rendered, hidden behind some opaque element (or above the content, with opacity:0 and pointer-events:none) and having it not displayed at all is insignificant.
Changes to your element will not trigger repaint on anything but itself, because it has position:fixed. The same would be true if it had position:absolute or if the changes to it would be made through properties that do not trigger repaint on subsequent siblings, like transform and opacity.
Unless the loader is really heavy on the rendering engine (which is rarely the case — think WebGL loaders with 3d scenes, materials and lights mapping — in which case it would be better to not display it when not shown to the user), the difference would be so small that the real challenge is to measure this difference, performance wise.
In fact, I would not be surprised if having it rendered and only changing its opacity and pointer-events properties is not, overall, less expensive than toggling its display property, because the browser doesn't have to add/remove it from DOM each time you turn it on/off. But, again, the real question is: how do we measure it?
Edit: Actually, I made a small testing tool, with 10k modals. I got the following results, in Chrome, on Linux:
`opacity` average: 110.71340000000076ms | count: 100
`display` average: 155.47145000000017ms | count: 100
... so my assumption was correct: display is more expensive overall.
The opacity changes are mostly around 110ms with few exceptions, while the display changes are faster when nodes are removed but slower when added.
Feel free to test it yourself, in different browsers, on different systems:
$(window).on('load', function () {
let displayAvg = 0, displayCount = 0,
opacityAvg = 0, opacityCount = 0;
for (let i = 0; i < 10000; i++) {
$('body').append($('<div />', {
class: 'modal',
html:'10k × modal instances'
}))
}
$(document)
.on('click', '#display', function () {
$('.modal').removeClass('opacity');
let t0 = performance.now();
$('.modal').toggleClass('display');
setTimeout(function () {
let t1 = performance.now();
displayAvg += (t1 - t0);
console.log(
'`display` toggle took ' +
(t1 - t0) +
'ms \n`display` average: ' +
(displayAvg / ++displayCount) +
'ms | count: ' +
displayCount
);
})
})
.on('click', '#opacity', function () {
$('.modal').removeClass('display');
let t0 = performance.now();
$('.modal').toggleClass('opacity');
setTimeout(function () {
let t1 = performance.now();
opacityAvg += (t1 - t0);
console.log(
'`opacity` + `pointer-events` toggle took ' +
(t1 - t0) +
'ms \n`opacity` average: ' +
(opacityAvg / ++opacityCount) +
'ms | count: ' +
opacityCount
);
});
})
});
body {
margin: 0;
}
.buttons-wrapper {
position: relative;
z-index: 1;
margin-top: 3rem;
}
.modal {
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
padding: 1rem;
}
.modal.display {
display: none;
}
.modal.opacity {
opacity: 0;
pointer-events: none;
}
.as-console-wrapper {
z-index: 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="buttons-wrapper">
<button id="display">Toggle `display`</button>
<button id="opacity">Toggle `opacity` + `pointer-events`</button>
</div>
But this average is for 10k elements. Divide it by 10k and it's virtually no difference at all: we're talking less than 0.45% of a millisecond.
If an element is animated with 'Animation' property and its duration is infinite browser will continuously repaint the site and that will affect the site performance and lower the FPS.
However hiding elements with properties such as Opacity:0; will not do the trick because element is still in CSSOM rendering tree queue.
Visibility:hidden; and display:none; should do the trick based on CSSOM construction browser doesn't render hidden elements such as display:none; & visibility:hidden

Proper way to sync setInterval and transition

Problem statement
To move the square along the perimeter of the viewport on click of the button as can be seen in the example:
https://codepen.io/vineetrok/pen/XRowdB
What do I need?
I'm using this code in combination with the transition property in the CSS. I think combination of transition and setInterval() is causing a delay. Is there a better and efficient method to accomplish this only using javascript?
Following is my code:
HTML
<div class="box" style="left:0;top:0"></div>
<button type="button" name="button" onclick="init()">Start!</button>
CSS
.box{
transition: all 1s linear;
}
JS
var elem = document.querySelector(".box");
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var dimension = elem.clientWidth;
var deltaX = viewportWidth - dimension;
var deltaY = viewportHeight - dimension;
function move(x,y){
if(x <=0 && y==0){
elem.style.left=(deltaX)+"px";
}
else if(x==(deltaX) && y==0){
elem.style.top=(deltaY)+"px";
}
else if(x==(deltaX) && y==(deltaY)){
elem.style.left="0px";
}
else if(x==0 && y==(deltaY)){
elem.style.top="0px";
}
}
function getCoordinates(elem){
return {
x: elem.getBoundingClientRect().left,
y: elem.getBoundingClientRect().top
}
}
var init = function(){
var clearTimer = 1;
var startTimer = setInterval(function(){
move(getCoordinates(elem).x,getCoordinates(elem).y )
}, 1000);
clearTimer++;
if(clearTimer>=4){
clearInterval(startTimer);
}
}
I would generally say that using both css and javascript to manage a transition is going to cause trouble. Part of the problem is that javascript timers aren't very precise. If you set a timer for 1 second it doesn't actually sleep for exactly one second. The exact amount of time it sleeps can vary depending on how busy the CPU is, what the user is doing, etc. It is very easy for the javascript timer to take longer than the CSS animation.
Since you are using jQuery I would use the jQuery.animate function to run things. It has a callback function that is invoked when the animation completes, and you can use that to execute the next step of the animation without any timers at all. That will make sure there aren't any delays. It should also be fairly performant. CSS animations are usually the slowest in terms of computer performance, so I expect jQuery.anmiate to probably be a bit better. There are other libraries out there designed for high performance animations, but unless performance actually becomes a problem, I wouldn't worry about it. Right now your issue is likely the imprecise timing of the timeout method, and not any performance issues.
Here's my go at it (I developed something from scratch instead of reusing your code) :
let box=document.getElementById("box"),
isLeft = false,
isTop = false
const toggleLeft = () => {
box.style.left = (isLeft=!isLeft) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleTop, 2000);
}
const toggleTop = () => {
box.style.top = (isTop=!isTop) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleLeft, 2000);
}
setTimeout(toggleLeft, 1000)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>
And a more condensed and recursive version :
let box=document.getElementById("box"),
is = { left : false, top : false }
const toggle = what => {
box.style[what] = (is[what]=!is[what]) ? "calc( 100% - 50px )" : "0";
setTimeout(()=>toggle(what==="left"?"top":"left"), 2000);
}
setTimeout(()=>toggle("left"), 100)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>

Running functions in intervals

I want to animate a set of elements (.col-n) in intervals. Let's say I have four .col elements (.col-1 etc) and I want to animate each of them class, but not after one is complete, but earlier.
I tried doing some for with setInterval, but it's not working (it's just looping):
container.children('.col').last().on('click', function() {
for(var i = 1; i <= columns; i++) {
var int = self.setInterval(function() {
console.log('a')
},500*i)
}
});
I tried each from jQuery with this and it's not this also. Can someone point me to right direction?
fiddle: http://jsfiddle.net/s5DMe/
Please note that I don't want to run functions one after another, but run one, wait (no matter if the first one has finished or not), run another one.
Okay, made it by myself ;) It was all the matter of using the interval and then creating a pseudo-loop inside:
container.children('.col').last().on('click', function() {
var i = 0;
setInterval(function() {
if(i != 4) {
i++;
$('.col-'+i).addClass('act')
}
},500)
});
Thanks anyway :)
.col-1 { background: #ccffff; transition-delay: 200ms }
.col-2 { background: #b3ffff; transition-delay: 400ms }
.col-3 { background: #99ffff; transition-delay: 600ms }
.col-4 { background: #80ffff; transition-delay: 800ms }
You can add your animating class to all of the .col-*'s at the same time. The transition-delay handles the faux sequencing.
Of course, you'll need to add the browser-prefixed versions of the property as well.

Categories

Resources