How to properly wait for browser reflow/repaint to finish - javascript

Let's say I have a complex HTML component that I want to animate, but every time it needs to be animated, several things need to be done, such as rendering new HTML components, setting height, attaching css classes, etc....
This could cause the animation not being smooth if the animation gets triggered in the middle of browser reflow/repaint. I could potentially use setTmeout to delay the animation, but how long it should wait is not clear.
Is there any bullet-proof way to wait for all these browser rendering work to be done?

Use window.requestAnimationFrame() (https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame) - you use it in essentially the same way you would use setTimeout, but you don't specify the delay - the browser deals with it for you.
Article about it from Paul Irish here: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Eg:
stepAnimation = function () {
// Do something to move to the next frame of the animation
// then
step()
}
step = function () {
window.requestAnimationFrame(stepAnimation)
}
step()

Related

SVG cursor with different shape elements hover

I am trying to implement code from this answer https://stackoverflow.com/a/67420648/7942242 which is a circle with organic mouvement.
The thing is that, it must move only when elements are hovered.
So on mouseover I call the function to make the movement with a parameter and the same the way out.
querySelect[i].addEventListener("mouseover", () => {
handleHover(true);
});
querySelect[i].addEventListener("mouseout", () => {
handleHover(false);
});
The function that updates the shape of my is update_blob which is called every 20 sec like so setInterval(() => update_blob(bool), 20)
With the variable bool from a parameter on the parent function (handleHover(false / true);)
But I don't get why it get stuck. I've tried many ways to make it work correctly and without boolean I don't see how I could achieve my goal.
It seems like the SVG has two state sometime animated and not at the same time.
Any thought on how to make the animation only on the hover ?
Find a full codesandbox bellow ⬇️
https://codesandbox.io/s/bold-cdn-yuddw
To resume my problem
I want to get this, but only when I hover my element. When it is not hovered it should be a simple circle that does not move.
When you set the interval, you're supposed to provide a function to call on each 'tick', you instead provide the outcome of a function-call:
setInterval(update_blob(bool), 20);
which should be:
setInterval(() => update_blob(bool), 20);
This will fix it for you. However, may I recommend you rewrite the function to use requestAnimationFrame instead? With setInterval, should the browser be under load, your function might exceed the 20ms interval, stacking up multiple calculations, further increasing the load of the device...
Using requestAnimationFrame you get time to run your function as soon as the browser deems it has time for you, and at the end of your calculation, you call the next frame, a much safer practice...

Dynamic SVG animation only works once

I have this animation setup to indicate which SVG was selected. The animation adds a svg:use element, and 3 animate or animateTransform elements within the svg:use element. Thanks to some great help here on SO I was able to get this working properly.
My new problem however is that the animation only works once as designed once. If a second element is selected, the animation appears to try to take place, as you can see the stroke-width increase, but the scale doesn't happen.
I thought this would be a simple fix by using a setTimeout to call a function and remove the svg:use after the animation completed. I wasn't so lucky.
An example of my problem can be seen here: http://codepen.io/JoeyCinAZ/pen/GHhbw
The function I wrote to remove the animation is here
setTimeout( function() {removeAnimation(objID)}, 5000);
function removeAnimation(objID) {
var useEl = document.getElementById(objID);
useEl.nextSibling.remove();
}
You've two issues within the animation. The simplest is duration, it can't per the SVG specification begin with a . so
flash.setAttributeNS(null, 'dur', '.5s');
is strictly speaking not valid and Firefox rejects it. I believe there are plans to change the SVG specification to match Chrome's laxer behaviour but until that happens write it as
flash.setAttributeNS(null, 'dur', '0.5s');
Secondly your main issue is that once you run the animation the document timeline goes from 0 to 5.5 seconds (that's how long all your animations take). The next time you create the animation, the document timeline is therefore at 5.5 seconds and the initial animation doesn't run because it's in the past as it's supposed to start at 0s. You could solve this either by
a) calling setCurrentTime to reset the timeline to 0, or
b) having the initial animation trigger from the button press event.
I had a similar issue before and solved it by completely removing the content of the element that contains the generated SVG, and then simply reload the new SVG in the empty element.
Instead of using a setTimeout which make the whole thing a bit weird, I would simply call it on clicking the element selector:
var elem = document.getElementById('myElementSelector');
elem.addEventListener('click', function() {
document.getElementById(surroundingElementID).innerHTML = "";
//Check what has been clicked and call function that creates the SVG on the surroundingElementID
}, false);

Replay jQuery function every 5 seconds

I want to replay my jquery function ChangeStats() every 5 seconds, it's currently doing sod all.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500);
setTimeout(function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
}, 500);
}
$(document).ready(function(){
setInterval(ChangeStats, 5000);
})();
Yes I have got the right class names.
No I haven't used underscores in my HTML.
I think it's something to do with my use of "find()", once the DOM has loaded and the function is set is it meant to traverse up the DOM tree instead of down?
EDIT:
Updated code, still not working.
HTML:
<span class="admin-stats-big-figures">%productCount%</span>
<span class="admin-stats-big-figures-hidden">hey</span>
Ok, I am going to go out on a limb and make several assumptions here; one is that you wish to cycle between two elements repeatedly, another is that you are using $(this) in the context of the window rather than a containing element. If either of these are incorrect then the following solution may not be suitable. However, let's give this a shot, eh?
1) You need to use setInterval rather than setTimeout to create a repeating call. You can of course "chain" your timeouts (ie: call the succeeding timeout from the code of the current timeout). This has some benefits in certain situations, but for now let's just assume you will use intervals rather than timeouts.
2) You call the find() jQuery method every time, which is a little unnecessary, especially if you will be repeating the actions so one idea would be to cache the lookup. If you are going to do that a custom object would be more suitable than separate global variables.
3) Some flexibility in terms of starting and stopping the animation could be provided. If we use a custom object as mentioned in (2) then that can easily be added.
4) You are using fadeIn and fadeOut, however if you wish the items to cycle then fadeToggle may be your best solution as it will simply allow you to do exactly that, toggle, without needing to check the current opacity state of the element.
5) Finally in my example I have provided a little extra "padding HTML" in order for the example to look good when run. Fading in jQuery will actually set the faded item to a CSS display of "none" which results in the content "jumping about" in this demo, so I have used some div's and a couple of HTML entity spaces to keep the formatting.
Ok, after all that here is the code..
// your custom animation object
var myAnim = {
// these will be cached variables used in the animation
elements : null,
interval : null,
// default values for fading and anim delays are set to allow them to be optional
delay : { fade: 500, anim: 200 },
// call the init() function in order to set the variables and trigger the animation
init : function(classNameOne, classNameTwo, fadeDelay, animDelay) {
this.elements = [$("."+classNameOne),$("."+classNameTwo)];
// if no fade and animation delays are provided (or if they are 0) the default ones are used
if (animDelay) this.delay.anim = animDelay;
if (fadeDelay) this.delay.fade= fadeDelay;
this.elements[0].fadeOut(function(){myAnim.start()});
},
// this is where the actual toggling happens, it uses the fadeToggle callback function to fade in/out one element once the previous fade has completed
update : function() {
this.elements[0].fadeToggle(this.delay.anim,function(el,delay){el.fadeToggle(delay)}(this.elements[1],this.delay.anim));
},
// the start() method allows you to (re)start the animation
start : function() {
if (this.interval) return; // do nothing if the animation is currently running
this.interval = setInterval(function(){myAnim.update()},this.delay.fade);
},
// and as you would expect the stop() stops it.
stop : function () {
if (!this.interval) return; // do nothing if the animation had already stopped
clearInterval(this.interval);
this.interval = null;
}
}
// this is the jQuery hook in order to run the animation the moment the document is ready
$(document).ready(
function(){
// the first two parameters are the two classnames of the elements
// the last two parameters are the delay between the animation repeating and the time taken for each animation (fade) to happen. The first one should always be bigger
myAnim.init("admin-stats-big-figures","admin-stats-big-figures-hidden",500,200);
}
);
OK, so now we need the HTML to compliment this (as I say I have added a little formatting):
<div><span class="admin-stats-big-figures">One</span> </div>
<div><span class="admin-stats-big-figures-hidden">Two</span> </div>
<hr/>
<input type="button" value="Start" onclick="myAnim.start()"/> | <input type="button" value="Stop" onclick="myAnim.stop()"/>
I have also provided buttons to stop/start the animation. You can see a working example at this JSFiddle - although the stop/start buttons are not working (presumably something specific to JSFiddle) they do work when in context though.
Here im gonna just replace your $(this). and maybe it'll work then.. also using callback.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500, function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
});
}
$(document).ready(function(){
setTimeout('ChangeStats()', 5000);
});

Execute the code but delay the 'return' of a function in JS

Am I able to, and if so how, delay the return of a Javascript function?
For example, say I use JS to set the background of an element that employs CSS3's transitions, and that transition takes 1 second, I'd like to return the function after the transition has finished (after 1 second).
Something like this (pseudo code):
function
element.style = 'background: #aaa; transition: all 1s ease-in;' // this will take 1 second to transition, although the JS is executed immediately
after 1 second // delay the return until the CSS3 transition completes.
return element
I hope this all makes sense! Thank you in advance, your help is much appreciated!
It might be helpful for everyone to see the actual code too: http://jsfiddle.net/BtNSs/1/
No, you're not able to do this. In browsers, JavaScript and the UI share the same thread, so the animation will not begin until your JavaScript code finishes executing.
If you want to do something after the animation completes, use the transitionend event.
How about supplying a callback to the function, and executing that callback after the required amount of time.
function doSomething(callback){
// execute your transition
setTimeout(callback,1000);
}
Live example: http://jsfiddle.net/eAMXK/ - you'll see it output the current time to the console, then a second later do the same.
You do not want to delay the return. You want to execute a callback after one second.
function (element) {
element.style = 'background: #aaa; transition: all 1s ease-in;'
setTimeout(function () {
// whatever you wanted to do with the element;
}, 1000);
}
Ideally, as #AndyE points out, you don't do it "after one second", but when the transitionend event occurs. This is more flexible and generally the right thing to do. Cross-browser support for that event is not yet soild, though. Depending on what you want to achieve, "after one second" might be a lot easier to implement.

A sticky situation for jQuery slideshow

I'm required to develop a slideshow (not an existing one) with jQuery. I was able to change picture with a function that I created named changePic (takes an image link). It incorporates the fading animation from the jQuery library.
For the slideshow I'm trying to use a while loop. It kind of works, except that it doesn't wait for the animation to finish.
How do I, a) wait for the animation to finish, b) delay the changing picture so it display the picture for a couple of seconds?
Also tried Settimeout, and it doesn't work.
Edit:
Basically changing image is like this:
function changePic(imglink){
var imgnode = document.getElementById("galleryimg");
$(imgnode).fadeTo(500, 0, function(){
$(imgnode).attr("src", imglink);
$(imgnode).fadeTo(1000, 1);
})
}
and the slideshow code is like this, but obviously it shouldn't.
function slideshow(gallerylinks){
var i=0;
while (i<gallerylinks.length){
changePic(gallerylinks[i]);
i++;
}
}
You could always try ditching the while loop, and going with a perpetually recursive function...
on the .animate, you could add a timeout function (at whatever interval) that calls the changePic function. As I have no idea what your code looks like, I will provide a fantastically generic outline.
/* array of imgUrls */
var imgUrls = new Array(); //populate it however
changePic(slideToShowIndex, fadeOutSpeed, fadeInSpeed, slideDelay)
{
$('#slideHolder').animate({ opacity: 0}, fadeOutSpeed , function(){
$('#slideHolder').attr('src', imgUrls[slideToShowIndex]);
$('#slideHolder').animate({ opacity: 1 }, fadeInSpeed, function() {
setTimeout(function() { changePic(slideToShowIndex+1, fadeOutSpeed, fadeInSpeed, slideDelay);}, slideDelay});
});
}});
}
$(document).ready(function() {
changePic(0, 5000, 5000, 10000);
});
This should (in theory) fade the image out, swap it with the new one, and fade it in (both taking 5 seconds) and then adding a delay to call itself with the next slide index in 10 seconds.
This is in no way perfect, but does outline the general idea. Since we have no idea what your code looks like, I can only assume your setTimeout was in the wrong spot. Doing it like this will make sure that the animation has finished before the timeout is set. This guarantees that the slide wont change until after the animation has changed.
of course you could always use a combination of the ':not(:animated)' selector and a setInterval to achieve much the same effect.
EDIT: made a slight change to stack the animations properly. The thoery behind this still works even with the OPs addition of code.
You could have provided more details or example code but have a look at stop() and delay() functions.

Categories

Resources