JavaScript animation is stuck, and will show only end result - javascript

I am trying to fiddle around and create a little fadeOut function in JavaScript,
This is what I came up with:
function fadeOut(id, time){
var elem = document.getElementById(id);
elem.style.opacity = 1;
var opc = 1;
while(opc >= (1/time)){
opc -= (1/time);
console.log(opc);
elem.style.opacity = opc;
}
elem.style.opacity = 0;
}
But this will not show the div's opacity in "real-time" but rather the end result, which is opacity = 0;
I've tested it out here:
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
fadeOut("hello",10000);
document.getElementById("hello").style.opacity = 1;
It would take it a long time to calculate and only when it finishes it will dump the result,
not showing it seamlessly, while calculating,
How can I resolve this?

You need to set timers, as, until your function is done and the event handler can run, the UI won't be updated.

It is because you are not 'yielding` the thread so as to allow the browser to apply the changes.
Use setTimeout(..) instead like this:
function fadeOut(id, time){
var elem = document.getElementById(id);
if(opc >= (1/time)){
opc -= (1/time);
console.log(opc);
elem.style.opacity = opc;
setTimeout(function(){fadeOut(id,time);}, 100);
}
else
elem.style.opacity = 0;
}
Not really a great code but it gives you the idea.
May be you could use jQuery library. In that case, you will use fadeOut

one potential causing this problem can be bubbling of event.try to use event.stopPropagation() to prevent the event (in case that you are using the FadeOut function in response to an event) from bubbling up.
the code is
function StopBubble(e)
{
if (!e)
e = window.event;
e.cancelBubble = true; /* Microsoft */
if (e.stopPropagation)
e.stopPropagation(); /* W3C */
}

You have to specify a delay between every modification you make to the DOM in order to see the effect. There is no option in javascript to add a pause feature, so you have to rely on timers. There are two ways to achieve this. Most commonly used approach is setInterval(fn,sec). But setInterval is bad, because it executes the callback every interval of time specified regardless the previous execution is complete or not.
I personally suggest using a library rather than re-inventing the wheel. But its always good to have a basic understanding what the libraries do behind the scene.
Below is a sample snippet to achieve this without setInterval.
Note: This is just for demonstration. Not cross browser compatible. Extend it for reusability and cross broswer compatibility.
<div id="mydiv"></div>
function fadeOut() {
var _timeOut,
_proceed = true,
_elm = document.getElementById( "mydiv" ),
_opacity;
function addOpacity() {
// _elm.style.opacity will return empty string
// see https://developer.mozilla.org/en-US/docs/DOM/window.getComputedStyle
// getComputedStyle() not cross broswer
_opacity = +window.getComputedStyle( _elm, null ).getPropertyValue( "opacity" );
if( _opacity > 0.1 ) {
// a simple algorithm for basic animation
_elm.style.opacity = _opacity / 2;
} else {
_elm.style.opacity = 0;
_proceed = false;
}
}
(function animate() {
if( !_proceed ) {
clearTimeout( _timeOut );
return;
}
addOpacity();
// unlike setInterval(), this approach waits for addOpacity() to complete before its executed again
_timeOut = setTimeout( animate, 1000 );
}());
}

Why not to use a JavaScript library like jQuery? Because some JavaScript code is not compatible with different sorts of browsers.
In jQuery you can use this:
$(".id").css("background","blue").fadeOut("slow");

Related

setInterval not stopping?

I thought an interval just delayed the function, but as it turns out it actually loops.
When I include some function that stops the interval after the deletor function ends it doesn't trigger that and I still get Test logged to the console.
document.addEventListener("DOMContentLoaded", function(event) {
let fullURL = window.location.href;
//let fullURL2 = window.location.host + window.location.pathname;
if (fullURL === "https://net.adjara.com/" ||
fullURL === "https://net.adjara.com/Home") {
var timer = setInterval(deletor, 5);
function deletor() {
timer;
var slider = document.querySelector("#slider-con");
var bannerTop = document.querySelector("#MainContent > div:nth-child(2)")
var bannerMiddle = document.querySelector("#MainContent > iframe");
var bannerRandom = document.querySelector("#MainContent > div:nth-child(3)");
if (slider) {
slider.parentNode.removeChild(slider);
}
if (bannerTop) {
bannerTop.parentNode.removeChild(bannerTop);
}
if (bannerMiddle) {
bannerMiddle.parentNode.removeChild(bannerMiddle);
}
if (bannerRandom) {
bannerRandom.parentNode.removeChild(bannerRandom);
}
function stopInterval() {
clearInterval(timer);
}
console.log("Test");
/*if ()
clearInterval(timer);*/
};
} else {
return false;
}
});
What you're looking for is setTimeout. It runs only once.
setTimeout(deletor, 5);
Also, you don't need to write timer variable inside of your closure like you would in Python. Javascript captures everything that's inside of lexical scope.
The code you provided works ¯\_(ツ)_/¯
Maybe the problem is coming from what triggers stopInterval()
But as mentioned in comments / other answers, you might be better off with another method
I wouldn't recommend using setTimeout in your case, because it looks like you are simply waiting for some DOM elements to be loaded. The problem with the timeout is that you can't know for sure how fast is the computer that will run your code. Maybe a bad quality phone with an outdated software that will need way more time to run your code than when you test on your personal computer, and that will not have the elements loaded by the time your function will be executed.
jQuery
For this reason, and since you tagged your question with jQuery I think you could use $(elementYouWaitForTheDOM2beLoaded).ready(function2execute) for each element you are watching instead of a having a loop that waits for the elements to be loaded (documentation for "ready" function)
Vanilla JS
And if you want to do it in pure JS it would be document.querySelector(elementYouWaitForTheDOM2beLoaded).on('load', function2execute))

waiting for requestAnimationFrame to finish (via callbacks)

So I'm new here, apologies if this is a basic question but I can't see it answered anywhere else.
I'm trying to write a webapp which shows an animation (using pixijs), and afterwards displays a div requesting a response. My issue is that because the animation is handled using requestAnimationFrame the animation occurs asynchronously, and so the response and animation phases occur simultaneously (the div appears instantly and obscures the animation).
Now I've looked around and the consensus seems to be to use a callback function, which only gets triggered after all the asynchronous work has been performed, allowing that serial progression.
HOWEVER, requestAnimationFrame takes the form
requestAnimationFrame(update_screen_objects),
but breaks when I try to do:
requestAnimationFrame(update_screen_objects(response_phase_callback))
as clearly requestAnimationFrame doesn't like being passed a function that itself has a callback. So my question is: what should I do to the animation loop to ensure that subsequent functions execute AFTER the animation is complete?
Thanks!
EDIT
This is an example of the code in the above form that doesn't work.
function animate(callback) {
var finished = false;
if ((new Date().getTime()) < adaptTime){
runFlicker(false);
} else if ((new Date().getTime()) < judgeTime){
hideAdaptors();
} else if ((new Date().getTime()) > judgeTime){
stage.visible = false;
finished = true;
}
renderer.render(stage);
if (!finished){
requestAnimationFrame( animate(callback) ); //Ensures it will keep looping
//requestAnimationFrame(animate); //This does work, but my issue still persists
} else {
callback(); //By the time callback() is required, you can't access it as requestAnimationFrame doesn't accept animate() with a function passed to it.
}
}
No need for a complex wrapper, just do:
requestAnimationFrame(update_screen_objects.bind(window, response_phase_callback))
This "currys" the update_screen_objects function by partially applying some arguments. The result of .bind(context, arg1) is a function that when called, only takes any arguments that aren't already bound, e.g arg2, arg3, etc.
Try this out. You basically need to generate that step (animate) function with the callback, instead of passing in the results of your call to animate, which would be undefined.
function generateAnimation(callback) {
function step() {
var finished = false;
var now = (new Date()).getTime();
if (now < adaptTime) {
runFlicker(false);
} else if (now < judgeTime) {
hideAdaptors();
} else if (now > judgeTime) {
stage.visible = false;
finished = true;
}
renderer.render(stage);
if (!finished) {
requestAnimationFrame(step);
} else {
callback();
}
}
return step;
}
// Usage:
requestAnimationFrame(generateAnimation(callback));

How to update a web page from javascript without exiting javascript

I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/

jQuery - Window resize; after

I am currently using jQuery's resize function, but because of what I adjust on resize, there's simply too much going on to make it look smooth, as it fires at every adjustment.
$(window).resize(function() {
myFunction();
});
Is there a way to fire a function off after the resize has stopped? Like $(window).afterResize() or something?
Any solutions welcome.
Set a timeout and do the action 100ms later, perhaps.
var timer;
$(window).resize(function() {
clearTimeout(timer);
timer = setTimeout(myFunction, 100);
});
I am not sure if there is a 'clean' native way to do it (hopefully there is and someone will shed light)
but you "hack" it like this http://jsfiddle.net/tuuN3/
var myInterval = false; // this variable will hold the interval and work as a flag
var $win = $(window); //jquery win object
var dimensions = [ $win.width(), $win.height() ]; //initial dimensions
$(window).resize(function() { //on window resize...
if( !myInterval ) //if the interval is not set,
{
myInterval = setInterval( function() { //initialize it
//and check to see if the dimenions have changed or remained the same
if( dimensions[ 0 ] === $win.width() && dimensions[ 1 ] === $win.height() )
{ //if they are the same, then we are no longer resizing the window
clearInterval( myInterval ); //deactivate the interval
myInterval = false; //use it as a flag
doStuff(); //call your callback function
}
else
{
dimensions[ 0 ] = $win.width(); //else keep the new dimensions
dimensions[ 1 ] = $win.height();
}
}, 64 ); //and perform a check every 64ms
}
});
You should research "debounce" or "throttle" as it relates to javascript. Depending on your needs - you may need to use one or the other. Here is a debounce/throttle library I've used as well as great descriptions of their applications. There is no need to re-invent the wheel - plus it is always great to know the terminology and identify useful coding patterns (for future maintainers of your code).

JavaScript timeline animation (setTimeout accuracy issues)

I began writing a simple animation class in JS, which utilizes Zepto.js animation capabilities but adds timeline-like capability to it.
The timeline itself is a simple array, that executes functions embedded in it when it's play() function is called:
play : function(callback){
for(var i=0; i<Animator.timeline.buffer.length; i++){
Animator.timeline.buffer[i].animation();
}
if(callback){
callback();
}
}
The setTimeout goes directly in the animation:
alpha : function(parameters, callback, delay){
var target = parameters.target;
var duration = parameters.duration;
var easing = parameters.easing;
var value = parameters.value;
if(delay){
setTimeout(function(){run();},delay*1000);
} else {
run();
}
function run(){
$(target).anim({opacity:value},duration,easing);
if(callback){
callback();
}
}
}
So basically, the timeline just runs the setTimeout-ed functions which are placed in it's buffer array.
This approach works (almost) as intended with WebKit animations, but i've run into a few problems when doing image sequences (animations using setInterval which change the image's src). As JS timers don't guarantee execution in their appointed time, sometimes animations run late, likely because of the embedded setInterval inside them.
Any ideas on how to solve this? I am aware that embedding all animations as callbacks inside one another would solve much of the issues, but i don't really know how to do that from inside the timeline loop. Also, it would quickly become an unreadable mess of callbacks if I call all functions in a direct manner (without using the timeline).
For reference, the sequence function of my animator class:
sequence : function(parameters, callback, delay){
var target = parameters.target;
var path = parameters.path;
var baseName = parameters.baseName;
var digits = parameters.digits;
var extension = parameters.extension;
var frames = parameters.frames;
var loop = parameters.loop;
if(parameters.interval){
var _interval = parameters.interval
} else {
var _interval = 15;
}
var currentFrame = 0;
var imageUrl = '';
var fileName = baseName;
for(var i=0; i<=digits; i++){
fileName+='0';
}
if(delay){
setTimeout(function(){runSequence();},delay*1000);
} else {
runSequence();
}
function runSequence(){
var interval = setInterval(function(){
if(currentFrame >= frames){
currentFrame = 0;
if(!loop) {
clearInterval(interval);
if(callback){
callback();
}
}
} else {
imageUrl = path+fileName.substring(0, fileName.length-currentFrame.toString().length)+currentFrame+"."+extension;
$(target).attr('src',imageUrl);
currentFrame++;
}
},_interval);
}
}
Also, a sample of an animation created by using this class:
Animator.timeline.append(function(){
Animator.alpha({'target':'#logo', 'value':1, 'duration':1, 'easing':'ease-out' });
});
Animator.timeline.append(function(){
Animator.sequence({'target':'#sequence', 'path':'images/sequences/index/', 'baseName':'nr1_', 'digits':3, 'extension':'png', 'frames':50},'',1.5);
});
Animator.timeline.append(function(){
Animator.scale({'target':'#text', 'width':.5, 'height':.15, 'duration':1, 'easing':'ease-in-out'},'',3.2);
});
Animator.timeline.append(function(){
Animator.alpha({'target':'#link', 'value':1, 'duration':1,'easing':'ease-out'},'',4.7);
});
Animator.timeline.play();
As an additional note, I was aiming to create something similar to GreenSock in AS3, if that helps.
Thanks.
Accurate setInterval can be simulated by compensating for the time it takes to execute every iteration, maybe this gist I wrote can help you:
https://gist.github.com/1185904

Categories