I'm wanting to animate an element using setInterval. I've put my code into an object with 2 functions one to initialize and one to animate using setInterval. When I try to run the code the animation works once then causes the browser to hang. The only thing I can think of is an infinite loop being created somewhere however I can't see anywhere in my code that would cause this.
What is causing the browser to crash, how can this be overcome ?
<div id='box' style='position:absolute;height:100px;width:100px;background-color:#44e'>box</div>
<script>
var box = {
init : function(elemId) {
box.elem = document.getElementById(elemId);
box.timer = setInterval;
box.tick = 0;
box.animate();
},
animate: function() {
if(box.tick < 100) {
box.elem.style.top = box.tick +'px';
box.elem.style.left = box.tick +'px';
box.tick++;
} else {
clearInterval(box.timer);
}
var timer = setInterval(box.animate, 50)
}
}
box.init('box');
</script>
setInterval sets up a function that will be called repeatedly by the browser until you cancel the interval timer. Your code isn't doing that, because the only call to clearInterval is using box.timer, which is never set to a timer handle (the return value from setInterval). So you end up scheduling thousands of calls (a new series every time animate is called) and bringing the browser to its kneeds.
At the very least, this:
var timer = setInterval(box.animate, 50)
should probably be:
box.timer = setInterval(box.animate, 50);
Or you may want setTimeout (which schedules only one call back).
Related
I know that this might be a stupid question but it drives me crazy. I'm trying to change the innerHTML of a DOM element but it doesn't change until the end of the function's execution. For example:
function test(){
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; i++)
{
testEl.innerHTML = 'Count: ' + i;
alert(i);
}
}
Even if I have put an alert in the loop, the text of the element will not change until the end of the function's execution. How can the change be applied instantly (for example I mean during the loop)?
You can update the number every period of time using setInterval:
function test(){
let testEl = document.getElementById('testEl');
let i = 0;
const interval = setInterval(function(){
testEl.innerHTML = `Count: ${i++}`;
if(i === 5)
clearInterval(interval);
}, 1000);
}
test();
<p id="testEl"></p>
JavaScript runs in a single-threaded environment. This means that only one execution context can ever be running at any single point in time. Asynchronous code executes outside of the JavaScript runtime environment (in this case by the browser's native processing) and only when the JavaScript thread is idle can the results of an asynchronous request be executed (i.e. callbacks).
Below is an example that updates a DOM element approximately every second, creating a clock. However, if you click the button, it will ask the browser to render an alert, which is handled outside of the JavaScript runtime and is a blocking UI element, so the clock will stop. Once you clear the alert, you will see the time jump to be roughly current.
As you'll see, the asynchronous API call to window.setInterval() allows for the function to run repeatedly, every so often, and therefore not continuously. This replaces the need for a loop that runs in its entirety every time its accessed. Because of this, you can see updates to the webpage instead of the last value of your loop.
See the comments for more details:
const clock = document.querySelector("span");
// setInterval is not JavaScript. It's a call to a browser
// API asking the JS runtime to run the supplied function every
// 900 milliseconds, but that's just a request. After 900
// milliseconds, the browser will place the function on the
// JavaScript event queue and only when the JavaScript thread
// is idle will anything on the queue be executed. This is why
// the 900 milliseconds is not a guarantee - - it's just the
// minimum amount of time you'll have to wait for the function
// to run, but it could be longer if what's already running
// on the JavaScript thread takes longer than 900 milliseconds
// to complete.
window.setInterval(function(){
// Update the DOM
clock.textContent = new Date().toLocaleTimeString();
}, 900);
document.querySelector("button").addEventListener("click", function(){
// An alert is also not JavaScript, but another browser API that is executed
// by the browser, not JavaScript. However, it is a blocking (modal) UI element.
// The rest of the browser interface (including the web page) cannot update
// while the alert is present. As soon as the alert is cleared, the UI will update.
window.alert("I'm a UI blocking construct rendered by the browser, not JavaScript");
});
<div>Current time is: <span></span></div>
<button>Click for alert</button>
Another way to achieve it is by using async - Promise like this
async function test() {
let testEl = document.getElementById('testEl')
for (let i = 0; i < 5; ++i) {
testEl.innerHTML = 'Count: ' + i;
await new Promise((resolve, _) => {
setTimeout(function() {
resolve("");
}, 1000 /* your preferred delay here */);
});
}
}
What I'd like to do is control the clock ticks for a non-realtime Cesium application. Imagine there's expensive code running, plus I want to give the viewer time to load the tiles before continuing. So how do I disable automatic ticking, and then call tick() manually when my code is ready for it?
Docs for Cesium.Clock say "The clock will only tick when both Clock#canAnimate and Clock#shouldAnimate are true." but that's not what I'm getting.
What I currently see:
viewer.clock.canAnimate = false;
viewer.clock.shouldAnimate = false;
viewer.clock.onTick.addEventListener(function(clock){
console.log("Tick");
});
The result in the console shows the clock still ticking:
Tick
Tick
Tick
Tick
...
What I'd like to do:
viewer.clock.stopTicking(); // or whatever that command would be...
while (someCondition){
// run expensive code
tick(); // issue manual tick
}
Thanks for your help!
Max
It's a bit of a legacy quirk of the Cesium API that the Clock's onTick event fires for every animation frame rendered, regardless of whether the clock advances in time or not.
If you want to take control of Cesium's render loop yourself, you can do that like this:
viewer.useDefaultRenderLoop = false;
function myOwnRenderLoop() {
viewer.resize();
viewer.render();
Cesium.requestAnimationFrame(myOwnRenderLoop);
}
Cesium.requestAnimationFrame(myOwnRenderLoop);
Above, I'm using requestAnimationFrame, so the loop runs as fast as possible. But I could replace that with setTimeout to get a slower loop, emulating poor render performance. Note that interactivity and screen updates would slow down with such a method when longer time intervals are used.
viewer.useDefaultRenderLoop = false;
function myOwnRenderLoop() {
viewer.resize();
viewer.render();
window.setTimeout(myOwnRenderLoop, 500);
}
window.setTimeout(myOwnRenderLoop, 500);
So, your console.log is still printing 'Tick' because the onTick continues to fire, regardless of whether the clock is advancing. All you need to do is toggle both the canAnimate and shouldAnimate, as you suspected. So, your example code would basically be:
viewer.clock.canAnimate = false;
viewer.clock.shouldAnimate = false;
while (someCondition){
// run expensive code
// toggle someCondition so we can exit this
}
// set the animate bools back to true so the clock can advance
viewer.clock.canAnimate = true;
viewer.clock.shouldAnimate = true;
To better see this in action, try this (and maybe set the if conditional to 1000 instead of 100):
viewer.clock.canAnimate = false;
viewer.clock.shouldAnimate = false;
var s = 0;
viewer.clock.onTick.addEventListener(function(clock){
if (s < 100) {
console.log(viewer.clock.currentTime);
} else {
viewer.clock.canAnimate = true;
viewer.clock.shouldAnimate = true;
}
s++;
});
You'll see that the console.log is printing the same value for 100 (or 1000) times...this is because the currentTime isn't advancing because of the canAnimate and shouldAnimate. Once those are both toggled back to true, the currentTime will advance.
I need to display a series of RGB colors inside my ASP.NET web page. I was hoping to accomplish this using an ASP Update Panel but the update has to take place in sequence without the user intervention, every 4 or 5 seconds.
My initial JavaScript code looks like this :
document.getElementById('div').style.backgroundColor = "rgb(0,0,255)";
Problem is getting the next RGB color combination to display.
I experimented with a wait 'loop' found here in another post :
wait(7000); //7 seconds in milliseconds
function wait(ms) {
var start = new Date().getTime();
var end = start;
while (end < start + ms) {
end = new Date().getTime();
}
}
Problem is that, it does not work. I tried with :
document.getElementById('fondRGB').style.backgroundColor = "rgb(0,0,255)";
wait(7000);
document.getElementById('fondRGB').style.backgroundColor = "rgb(128,128,128)";
wait(7000);
document.getElementById('fondRGB').style.backgroundColor = "rgb(0,0,0)";
but only the very first rgb color is ever honored?
running a blocking while loop in javascript is a really bad idea. There is only one thread so if you do that the user will not be able to interact with your application until your while loop is complete.
A better solution is to use setTimeout to run the timer asynchronously and call the callback when the timer is complete.
var el = document.getElementById('fondRGB')
function wait(ms, callback) {
setTimeout(callback, ms)
}
wait(7000, function(){
el.style.backgroundColor = "rgb(0,0,255)"
})
wait(14000, function(){
el.style.backgroundColor = "rgb(128,128,128)"
})
wait(21000, function(){
el.style.backgroundColor = "rgb(0,0,0)"
})
So even if that did work it wouldn't be what you want since the wait function is attempting to block the only execution thread which would just make the whole browser hang. The wait method you are looking for is called setTimeout and it works a little different than you are probably used to.
setTimout lets you specify a function and an amount of milliseconds before it will execute that function so in your case if you want to accomplish changing the background color every 7 seconds you would probably do something like this:
var colors = ['rgb(0,0,255)', 'rgb(128,128,128)', 'rgb(0,0,0)'];
for (var i = 0; i < colors.length; i++) {
setTimeout(function(color) {
document.getElementById('fondRGB').style.backgroundColor = color;
}.bind(null, colors[i]), i * 7000);
}
Sorry that the code got a little complex, look up closures if you are wondering why the .bind part is necessary (if you are not already familiar).
I am trying to make a function that starts in exact intervals to keep stanble update rate. The problem is that it seems to execute in 2 channels. This is the log:
timeElapsed=141; lastDraw=1314040922291
timeElapsed=860; lastDraw=1314040923151
timeElapsed=141; lastDraw=1314040923292
timeElapsed=860; lastDraw=1314040924152
timeElapsed=141; lastDraw=1314040924293
timeElapsed=860; lastDraw=1314040925153
timeElapsed=141; lastDraw=1314040925294
timeElapsed=860; lastDraw=1314040926154
timeElapsed=141; lastDraw=1314040926295
timeElapsed=859; lastDraw=1314040927154
timeElapsed=143; lastDraw=1314040927297
timeElapsed=858; lastDraw=1314040928155
timeElapsed=143; lastDraw=1314040928298
timeElapsed=858; lastDraw=1314040929156
timeElapsed=142; lastDraw=1314040929298
First, I exectute my function using
drawTimer = setTimeout(function(){ draw() }, 1);
and the function looks like this:
var draw = function(){
if(!running)
return;
var miliseconds = getCurrentMiliseconds();
var timeElapsed = miliseconds - lastDraw;
lastDraw = miliseconds;
console.log("timeElapsed=" + timeElapsed + "; lastDraw=" + lastDraw);
onDrawListener(timeElapsed);
if(timeElapsed < timeLapse)
miliseconds = timeLapse - timeElapsed;
else
miliseconds = 1;
drawTimer = setTimeout(function(){ draw() }, miliseconds);
}
It happens in both, Chrome and Firefox. Do you know what is it caused by? And... How to fix it?
P.S. Since everyone seems to be so confused about the running variable, here it is: it's a private parent object member that indicates whether the mechanism is still running or has stopped. It's set by other functions and is just there to make sure this function doesn't continue working after stop() is called.
-- update --
timeLapse is set to 1000 (1 time per seconds) and never changed again.
onDrawListener is set to this function:
function(timeElapsed){
canvas.clear();
moveSnake();
if(snake.body[0] == food){
food = getRandomFreePosition();
++snake.body.lenght;
}
drawBackground();
drawFood();
drawSnake();
}
to explain it: canvas is kinda the engine that takes care of callbacks, key listening and also has a few functions. Other than that seems kinda self-explaining. they do nothing other than some int algorithms and drawing in the canvas.
-- Figured out --
I understood that I should calculate time spent for current function and not since the last one started. My old method worked not in 2 channels but rather in long-short-long-short-long-... delayes
first of all you dont set the running bool and also when you enter the function immediately do a on clearTimeout on drawTimer.
clearTimeout(drawTimer);
In a loop like that, you should consider to write:
if(timeElapsed >= AMOUNT_OF_TIME)
{
// run code
}
I have some javascript functions that take about 1 to 3 seconds. (some loops or mooML templating code.)
During this time, the browser is just frozen. I tried showing a "loading" animation (gif image) before starting the operation and hiding it afterwords. but it just doesnt work. The browser freezes before it could render the image and hides it immediately when the function ends.
Is there anything I can do to tell the browser to update the screen before going into javascript execution., Something like Application.DoEvents or background worker threads.
So any comments/suggestions about how to show javascript execution progress. My primary target browser is IE6, but should also work on all latest browsers
This is due to everything in IE6 being executed in the same thread - even animating the gif.
The only way to ensure that the gif is displayed prior to starting is by detaching the execution.
function longRunningProcess(){
....
hideGif();
}
displayGif();
window.setTimeout(longRunningProcess, 0);
But this will still render the browser frozen while longRunningProcess executes.
In order to allow interaction you will have to break your code in to smaller fragments, perhaps like this
var process = {
steps: [
function(){
// step 1
// display gif
},
function(){
// step 2
},
function(){
// step 3
},
function(){
// step 4
// hide gif
}
],
index: 0,
nextStep: function(){
this.steps[this.index++]();
if (this.index != this.steps.length) {
var me = this;
window.setTimeout(function(){
me.nextStep();
}, 0);
}
}
};
process.nextStep();
Perhaps you can put in a delay between showing the animated gif and running the heavy code.
Show the gif, and call:
window.setTimeout(myFunction, 100)
Do the heavy stuff in "myFunction".
You have to use a little more sophisticated technique to show the progress of the long running function.
Let's say you have a function like this that runs long enough:
function longLoop() {
for (var i = 0; i < 100; i++) {
// Here the actual "long" code
}
}
To keep the interface responsive and to show progress (also to avoid "script is taking too long..." messages in some browsers) you have to split the execution into the several parts.
function longLoop() {
// We get the loopStart variable from the _function_ instance.
// arguments.callee - a reference to function longLoop in this scope
var loopStart = arguments.callee.start || 0;
// Then we're not doing the whole loop, but only 10% of it
// note that we're not starting from 0, but from the point where we finished last
for (var i = loopStart; i < loopStart + 10; i++) {
// Here the actual "long" code
}
// Next time we'll start from the next index
var next = arguments.callee.start = loopStart + 10;
if (next < 100) {
updateProgress(next); // Draw progress bar, whatever.
setTimeout(arguments.callee, 10);
}
}
I haven't tested this actual code, but I have used this technique before.
Try setting a wait cursor before you run the function, and removing it afterwards. In jQuery you can do it this way:
var body = $('body');
body.css("cursor", "wait");
lengthyProcess();
body.css("cursor", "");