Make javascript Animation function loop, Until page loads - javascript

I have a piece of javascript that I have copied & edited, that is designed for an animated loading ring but the animation only runs once, I would like it to run every 4 seconds, until the page is loaded, but I can't find the right syntax/script to get it to repeat, i do not want it to reload the page only loop that specific script until i set it to stop.
".radial" is the class of the radials contained inside my css & html files.
there is twelve of them & they do-not rotate only the fluorescent .glow animation part makes it appear as they are rotating. the code is;
const radials = [...document.querySelectorAll('.radial')];
let degrees = 29;
for(i=0; i < radials.length; i++) {
degrees += 13;
radials[i].style.transform = `rotate(${degrees}deg)`;
degrees += 34;
}
radials.forEach((radial, index) => {
setTimeout(function() {
radial.classList.add('glow');
},index * 29);
});
:: Update ::
Having read the comments below and searching on Youtube. I think that wrapping the whole script in a function, would be the best option. Including a call to that function within its self & passing it an argument in the parenthesis of a timeout or delay property. But setInterval() & setTimeOut() both use the unsafe eval() function underneath. Which is supposed to be a security concern.
Also a youtube video I watch a while ago, said that setInterval() & setTimeOut() do not achieve 60fps. requestAnimationFrame() Would be A much better option. I'm not sure how legitamate these claims are, or where his sources were from but I will continue searching the Webs.
The glow part looks good but I just haven't been able to get it to repeat.
I am new to Js please be patient.
is there any other workarounds for the setTimeOut() & setInterval().?

Place this code into a function that is passed to a setInterval() timer call.
function loop() {
const radials = [...document.querySelectorAll('.radial')];
let degrees = 29;
for(i=0; i < radials.length; i++) {
degrees += 13;
radials[i].style.transform = `rotate(${degrees}deg)`;
degrees += 34;
}
radials.forEach((radial, index) => {
setTimeout(function() {
radial.classList.add('glow');
},index * 29);
});
setTimeout(loop, 4000);
}

Use setInterval(). The setInterval takes two parameters, the first is the function you want to run and the second is your repeat time in miliseconds. So to run a function every 4 seconds you would do:
setInterval(function() {
// do something
}, 4000);

You can do it with setInterval, as in the other answers, but I think that the logic is clearer if you have an animate function that keeps calling itself.
You are adding a "glow" class, but you are never removing it. The animate function should toggle it on and off. To make it crystal clear, let's make that a separate function, toggleGlow.
Next, each animation loop we kick off the individual toggleGlow functions with a different delay for each radial.
Finally, the animate function will re-call itself after a short, constant, delay each time, until some stop condition is met (like the page loading).
const radials = [...document.querySelectorAll('.radial')];
function toggleGlow(element) {
if (element.classList.contains("glow")) {
element.classList.remove("glow");
} else {
element.classList.add("glow");
}
}
function animate() {
radials.forEach((radial, index) => {
setTimeout(function() {
toggleGlow(radial);
}, index * 29);
});
if (!stopCondition) {
setTimeout(animate, 200);
}
}
// kick it off
animate();
JSFiddle example here: https://jsfiddle.net/duxhy3Lj/

Related

How to coordinate timing for sound and animation

I'm trying to add sound and animation to this "ping-pong" (fizz-buzz) program. What I want to happen is for each item in the array (number/ping/pong/pingpong) the text fades in on the list while the sound and animation play.
What I'm getting is - the whole list fades in all at once, the sounds all play one at a time (and if the number entered is large it goes on forever) but the animation happens only once (apparently for just the last animation).
The whole project is at: https://github.com/karenfreemansmith/Epic-AdvancedJSwk1-PingPongCalculator, along with a link to a page with what is currently working. (Slightly earlier than the code below, which has only broken it in new ways.)
I've been trying to use setInterval and setTimeout to sync them all by calling a function that will show one element at a time with it's sound and animation:
var play=setInterval(function() {
var i=1;
output.forEach(item => {
showNext(item);
if(i>=output.length) {
clearInterval(play);
}
i++;
});
}, 1000);
And the function looks like this:
function showNext(item) {
acorn.style.animation= "";
acorn.style.webkitAnimation="";
if(item==="ping") {
window.setTimeout(playPing(), 1000);
} else if(item==="pong") {
window.setTimeout(playPong(), 1000);
} else if(item==="ping-pong") {
window.setTimeout(playVolley(), 1000);
} else {
window.setTimeout(playMiss(), 1000);
$("#pingpong").append("<p class='"+item+"'>" + item + "</p>");
}
}
and the play functions are all basically the same, but with different sounds:
function playPing() {
acorn.style.animation= "ping 1s linear";
acorn.style.webkitAnimation="ping 1s linear";
sndSlam1.currentTime = 0;
sndSlam1.play();
$("#pingpong").append("<p class='ping'>ping</p>");
}
I think I must be misunderstanding how the setTimeout is working. Why does the animation only play once? And why is there no pause between the elements being added to the list?
The problem here is because, yes, you do slightly misunderstand how setTimeout works.
What you do is call it like this:
window.setTimeout(playVolley(), 1000);
Which is equivalent to saying: "hey JS, immediately execute my function playVolley (since I use () to specify that I want it called), and THEN in 1000 seconds call whatever it has returned".
What, I strongly suspect, you really wanted to do, is:
window.setTimeout(playVolley, 1000);
Note how there are no "()" after playVolley. This is equivalent to saying: "hey JS, in 1000 seconds execute my cool func called playVolley".
If "passing function name without ()" doesn't make sense to you, that's okay, just read about "functions as first-class objects" (for example, here). The idea is just any function is really like a variable which holds a "function" in it, and you can pass it to anything takes "function as an input. Which, for example, setTimeout does - it needs a "function" and an "integer" to set a timeout.
But only fixing this won't help you. There's another problem here:
output.forEach(item => {
showNext(item);
...
}
See, here you effectively set output.length timeouts, all of them at once, to fire in 1000 seconds. Which they will do - in 1000 all of them will be executed simultaneously. So all you'll fix by the first fix is that all of your animations and sounds will play not immediately, but after a 1000ms delay.
What, I again strongly suspect, you wanted to do is to call every step of output array one by one, with 1000 delay between each other.
To achieve this you'll need to refactor the way you schedule your calls. Instead of scheduling them all at once, you'll need to chain them. A dirty, but simple example would be to have an index to current animation step, and when your playXXX finishes, it schedules next step to run, until all the steps are completed.
var currentAnimationStep = 0;
var output = ["ping", "pong", "ping", "pong"];
snowNext(output[currentAnimationStep]);
function showNext(item) {
+ if (item === undefined) {
+ return;
+ }
+
...
}
function playPing() {
acorn.style.animation= "ping 1s linear";
acorn.style.webkitAnimation="ping 1s linear";
sndSlam1.currentTime = 0;
sndSlam1.play();
$("#pingpong").append("<p class='ping'>ping</p>");
+ currentAnimationStep += 1;
+ showNext(output[currentAnimationStep]);
}
// All other playXXX functions will need the same call added
Again, this is a very dirty example (globals, eeeew), don't tell anybody I showed you this, but it can get you started, and when you'll get a hang of closures, you'll rewrite it to something more manageable.

Resolving latency with setTimeout for Drum Machine

I am currently working on a drum machine and I am using setTimeout to make it run. Here is the heart of the code:
var sequencerRun = function(){
var currentTime = 0
var starting = 200;
for(var k = 0; k < 16; k++){
$(".instrument td .beat" + k).each(function(){
setTimeout(blinker, currentTime,$(this));
})
currentTime += starting;
}
}
var timerId, setInt;
var runSeq = function(){
setInt = setInterval(sequencerRun,3200);
}
$('.play').click(function(){
stopped = false
sequencerRun();
runSeq();
})
$('.stop').click(function(){
clearInterval(setInt);
stopped = true;
})
The drum machine has a matrix HTML structure built using a table. When .play is clicked a scheduling process occurs, which is encapsulated in sequencerRun. This involves a run through the columns of my matrix to determine whether there should be a drum hit or not. This is done through blinker. The scheduling creates a check on each column 1 - 16 at times 0,200,...,3200 respectively. This is what creates the effect of a sequencer. I also have a setInterval that reruns this process every 3200, which is how it takes for a run to finish.
Programmatically my code seems to make sense and my hope was that it would execute on time. The thing is that my actual app tends to stutter a lot and is stuttering even more since I deployed it. Here is a deployed version of my app.
This stuttering side effect can be best heard when you click on a full row. My question here is can anyone tell if this side effect is a result of setTimeout's timing inconsistency and if so how could I go about fixing this? Or is this related to something else that I am missing?
I think the stuttering issue has more to do with you not preloading the instruments but rather loading them on every hit, more than it has to do with settimeout.
In any case, I think I would have solved this differently. Rather than setting a fresh timeout for each beat, create one beat timeout and put the logic in there. Something like (pseudo-code-ish, lots of stuff missing just the general idea):
var MAX_BEATS = 16; // I think you had 16 beats in your example?
var BPM = 200;
var preloadedInstruments = [];
function preloadInstruments(){
for(i of myInstruments) { // myInstruments would be a list of your instruments (probably just strings with a filename)
preloadedInstruments.push(new instrument(i)); // You need an instrument class, that would preload the sound and provide a play function
}
}
var currentbeat = 1;
function beat() {
var activeInstruments = getActiveInstruments(currentbeat); // You could cache this also for every beat, but I think a simple loop here would be quick enough
for(instrument of activeInstruments) {
preloadedInstruments[instrument].play(); // play method of the instrument class called
}
currentbeat++;
if (currentbeat > MAX_BEATS) {
currentbeat = 1;
}
}
setInterval(beat, 60e3 / BPM);

Updating style at beginning of button click code; removing after execution

I have a block of code that executes when a button is clicked. The code uses a loop that sometimes takes a while to complete. When the user clicks the button, I want the cursor to change a "wait" cursor before the loop begins. Once the loop is finished, the cursor should return to normal.
What is actually happening (in Chrome for Windows at least) is that the style doesn't get updated until after the loop. It seems to be a quirk of how buttons work. I really don't know. I'm out of guesses!
A sample fiddle: http://jsfiddle.net/ra51npjr/1/ (it just uses console.log to execute "something"... you might need to change how many times the loop runs depending on how zippy or slow your machine is).
Sample HTML:
<div class="fakebody">
<button id="foo">Foo</button>
</div>
Sample CSS:
.fakeBody {
height: 1000px;
width: 100%;
}
.wait {
cursor: wait !important;
}
Sample JavaScript:
$('#foo').on('click', function (e) {
$('.fakebody').addClass('wait');
for (i = 0; i < 10000; i++) {
console.log(i);
}
$('.fakebody').removeClass('wait');
});
--
Here are my ASSUMPTIONS on how the script should work:
The click happens, which fires up the code. Indeed, if I log "started!" inside the code block, it will correctly log that it has started
The cursor should be a wait cursor so long as it is hovering anywhere over "fakebody".
The for loop is just a simple way to kill a few seconds to see the effect. Feel free to substitute any other loop that takes a while to complete
At the end of the loop, the cursor is no longer a wait cursor
What is actually happening:
The loop executes
At the end of the loop, the cursor turns to a "wait" cursor and then instantly back to a regular cursor. The change doesn't happen until the loop is complete
Does anybody know a technique or workaround to get the cursor to change before the loop starts instead of only after it is finished? Is this known behaviour that I need to educate myself about (and if so, do you know where I should start looking?)
This is a common issue in JavaScript. This question may provide some deeper insight, but essentially the point is that synchronous JavaScript execution must finish before the browser can perform other actions (like updating the view).
Because .addClass, the for loop, and .removeClass all occur synchronously, the browser doesn't get a chance to redraw anything. A technique that is often used in these cases is to setTimeout with a timeout of 0, which essentially just "yields" control back to the browser.
$('.fakebody').addClass('wait');
setTimeout(function() {
for (i = 0; i < 10000; i++) {
console.log(i);
}
$('.fakebody').removeClass('wait');
}, 0);
If this is a common pattern, you could potentially extract it out to a function (which would also help improve readability) that wraps the async setTimeout. Here's a simple example:
/**
* Wraps a long-running JavaScript process in a setTimeout
* which yields to allow the browser to process events, e.g. redraw
*/
function yieldLongRunning(preFn, fn, postFn, ctx) {
if (arguments.length <= 2) {
ctx = fn; fn = preFn;
preFn = postFn = function() {};
}
preFn.call(ctx);
setTimeout(function() {
fn.call(ctx);
postFn.call(ctx);
}, 0);
}
And use it like so:
yieldLongRunning(function() {
$('.fakebody').addClass('wait');
},
function() {
for (i = 0; i < 10000; i++) {
console.log(i);
}
},
function() {
$('.fakebody').removeClass('wait');
});
As a side point, note that setTimeout(..., 0) simply queues the function in the browser's event loop, alongside other queued JavaScript functions, as well as other types of events (like redraws). Thus, no setTimeout call is guaranteed to run precisely at the given time - the timeout argument is simply a lower-bound (and, in fact, there is a minimum timeout of 4ms specified by HTML5 spec, which browsers use to prevent infinite timeout loops; you can still use 0, though, and the browser will add it to the event queue after the minimum delay).
I think you should try to force a redraw by hiding + showing the parent element.
Try this:
document.getElementById('fakebody').style.display = 'none';
document.getElementById('fakebody').style.display = 'block';
Before and after the loop (i.e. when you want the child element "foo" to refresh.
EDIT: Since you're using jquery you could do this:
$('#fakebody').hide().show(0);
Demo - Use queue & dequeue to construct an order of what should happen when in jQuery.
$('#foo').on('click', function (e) {
$('.fakebody').addClass('wait').queue(function(n) {
for (i = 0; i < 10000; i++) { console.log(i); }
}).removeClass('wait').dequeue();
});

Javascript while loop changing scroll position of div crashes site

I have a div displaying some horizontally scrollable images with white-space:nowrap; overflow-x:scroll and i'm trying to make the function below work:
var mouseIsInDiv = false;
function autoScroll() {
var i = 1;
while (mouseIsInDiv = false) {
setTimeout(function(){
document.getElementById("theDiv").scrollLeft = i;
i++;
},50);
}
}
It is supposed to loop through (while the mouse is not within this scrollable div) incrementing the scroll position by 1px every 50 miliseconds. In other words it's supposed to scroll through the images automatically when this function is called. I'm not getting any syntactic errors but whenever i press a button that calls this function on a webpage, the browser crashes completely - I'm using the latest versions of Chrome, Safari and Firefox. Any ideas would be really helpful, I've been tearing my hair out over this!
Your loop creates many timeouts that happens in the same time (after 50 milisecs) you need to set the timeout recursivly, inside the set timeout function, and ask if mouseISInDiv inside the set timeout function as well.
The current code state, the loop will run many many times in a small amount of time, and page will crush(it's liek infinite) and after 50 millisecs there will be many set timeouts that ran.
I had a fun time working on this one, so I'll post my response despite the correct answer already having been accepted.
Basically, you need to restructure everything so that the whole scheme is asynchronous. That means event listeners respond to mouse movement, and there are no while loops.
Thus, I present this fiddle. Here is the javascript:
var mouseIsInDiv = false;
var theDiv = document.getElementById("theDiv");
theDiv.onmouseover = function() { mouseIsInDiv = true; };
theDiv.onmouseout = function() {
mouseIsInDiv = false;
scrollLeft1();
};
function scrollLeft1() {
if (mouseIsInDiv == false && theDiv.scrollLeft < theDiv.clientWidth) {
theDiv.scrollLeft += 1;
setTimeout(scrollLeft1, 50);
}
}
scrollLeft1();
As you can see, the function calls itself recursively and asynchronously, and the whole thing can be restarted after manually resetting the scroll. You could also add an event listener for the scroll completion.

Sequencing Events in Javascript

I am trying to make a simple hidden object game using javascript. When the user finds and clicks an image, I want 3 things to happen in the following order; a sound plays, the image size increases, and the image goes invisible. The problem I am running into is getting the 3 events to happen sequentially, not concurrent. Right now, seems that all three events happen all at the same time.
I've tried using setTimeout(), and while that does create a delay, it still runs all functions at the same time, even if each function is nested in setTimeout.
Example: (all this does is waits 1.5 sec then plays the sound and makes the image invisible):
function FindIt(image, id){
var t = setTimeout('sound()',10);
var b = setTimeout('bigger(' + image + ')',30);
var h = setTimeout('hide(' + image + ')',1500);
}
Below are the functions I am currently using and the actual results are: click the image, nothing happens for 2 seconds, then the sound plays and the image goes invisible.
function FindIt(image, id){
sound();
bigger(image);
hide(image);
}
function sound(){
document.getElementById("sound_element").innerHTML= "<embed src='chime.wav' hidden=true autostart=true loop=false>";
}
function bigger(image){
var img = document.getElementById(image);
img.style.width = 112;
img.style.height = 112;
}
function hide(id){
var ms = 2000;
ms += new Date().getTime();
while (new Date() < ms){} //Create a 2 second delay
var img = document.getElementById(id);
img.style.visibility='hidden';
}
Any guidance would be greatly appreciated!
To trigger things sequentially, you need to execute the second item some amount of time after the first one completes, execute the third item some amount of time after the second one completes, etc...
Only your sound() function actually takes some time, so I'd suggest the following:
function FindIt(image, id){
sound();
// set timer to start next action a certain time after the sound starts
setTimeout(function() {
bigger(image);
// set timer to start next action a certain time after making the image bigger
setTimeout (function() {
hide(image);
}, 1000); // set this time for how long you want to wait after bigger, before hide
}, 1000); // set the time here for how long you want to wait after starting the sound before making it bigger
}
FYI, the animation capabilities in libraries like jQuery or YUI make this sort of thing a lot easier.
Also, please don't use this kind of construct in your JS:
while (new Date() < ms){}
That locks up the browser for that delay and is very unfriendly to the viewer. Use setTimeout to create a delay.
For reference, using the animation libraries in jQuery, the jQuery code to handle a click on the object and then animate it over a 2 second period to a larger size, delay for 1 second, then slideup to disappear is as follows:
$("#rect").click(function() {
$(this).animate({height: 200, width: 400}, 2000).delay(1000).slideUp();
});
jQuery manages an animation queue and handles setting all the timers and doing all the sequencing and animation for you. It's a lot, lot easier to program and gives a very nice result.
You can see it work and play with it here: http://jsfiddle.net/kC4Mz/.
why don't use "event" approach. like onTaskDone();
function task1(arg, onTask1Done){
console.log(arg);
if(onTask1Done)onTask1Done();
}
task1("working", function(){console.log("task2");});
The Frame.js library is designed to elegantly handle situations like this:
function FindIt(image, id){
Frame(10, function(next) { sound(); next(); });
Frame(30, function(next) { bigger(image); next(); });
Frame(1500, function(next) { hide(image); next(); });
Frame.start();
}
Frame.js offers many advantages over using standard timeouts, especially if you are doing a lot of this kind of thing, which for a game, you likely are.
https://github.com/bishopZ/Frame.js

Categories

Resources