How does javascript update the elements in a DOM? - javascript

I am trying to slow down an animation of a picture shrinking so that you can actually see it shrink instead of just jump to a smaller size, but what instead happens is that my console log updates as it should-with intervals in-between each update, but the image on the screen waits until the for loop is done and then jumps to the end size instead of gradually getting smaller. Is there something wrong with my code or am I missing some behind the scenes stuff on how the page updates?
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
function makeSmaller(){
for (let i = 100; i>50; i--){
rockButtonImage.style.width = i+'%';
console.log(rockButtonImage.style.width);
sleep(50);
}
}

Modern browsers avoid unnecessary renderings of a page and wait while a JavaScript execution iteration is complete before doing a rendition.
The sleep() function in the provided code does not really cause a browser to sleep but rather to do a processor-intensive computation, forbidding the browser from rendering anything.
You can try using the setTimeout() function instead, e.g.:
var i = 100;
function makeSmaller(){
console.log(rockButtonImage.style.width);
rockButtonImage.style.width = i+'%';
i--;
if (i > 50) {
setTimeout(makeSmaller, 50);
}
}
You might also find it useful to rely on CSS animations or CSS transitions instead of JavaScript for such visual effects as JS is not nearly as efficient as they are.
See also:
More on setTimeout: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
More about the CSS animations: https://developer.mozilla.org/en-US/docs/Web/CSS/animation

You have to dynamically get the DOM element getElementById and then change the width. Im not sure in which context the RockButton variable lives. Its all about the state.

Related

Update webpage to show progress while javascript is running in in a loop

I have written javascript that takes 20-30 seconds to process and I want to show the progress by updating the progress bar on my webpage.
I have used setTimeout in an attempt to allow webpage to be re-drawn.
This is how my code looks like:
function lengthyFun(...){
for(...){
var progress = ...
document.getElementById('progress-bar').setAttribute('style',"width:{0}%".format(Math.ceil(progress)));
var x = ...
// Processing
setTimeout(function(x) { return function() { ... }; }(x), 0);
}
}
It does not work, I know why it does not work, but I don't know how to refactor my code to make it work.
As you probably know, the problem here is that you main process (the one that takes a lot of time), is blocking any rendering. That's because JavaScript is (mostly) mono-threaded.
From my point of view, you have two solutions to do this.
The first one is to cut down your main process into different parts and to do the rendering between each of them. I.e. you could have something like that (using Promises) :
var processParts = [/* array of func returning promises */];
function start(){
// call the first process parts
var firstPartPromise = (processParts.shift())();
// chain it with all the other process parts interspersed by updateDisplay
return processParts.reduce(function(prev, current){
return val.then(current).then(updateDisplay);
}, firstPartPromise);
}
You will probably need a polyfill for the promises (one here). If you use jQuery, they have a (bad non standard) implementation.
The second solution can be to use webworkers which allows you to create threads in JavaScript. It works on all modern browsers.
It is probably the best solution in your case.
I never used them but you are supposed to be able to do stuff like:
var process = new Worker("process.js");
worker.onmessage(function(event){
updateProgress(event.data.progress)
});
And the in process.js:
postMessage({progress: 0.1});
// stuff
postMessage({progress: 0.4});
// stuff
postMessage({progress: 0.7});
//etc
Try setting progress element attribute min to 0 , max to 20000 , value to 0 ; create function where if value less than max increment value by 1000 ; utilize setTimeout with duration set to 1000 to call function recursively until value reaches max
var p = document.querySelector("progress");
function redraw() {
if (p.value < p.max) {
p.value += 1000;
setTimeout("redraw()", 1000)
}
}
redraw()
<progress max="20000" min="0" value="0"></progress>
There are a couple of ways that I know of to trigger sequential HTML redraws through Javascript:
Incremental Timeout Period
Recursive Method Calls
The first and easiest way of doing this is by using a multiplier (such as the iterator) on the timeout interval in a loop. This method should be sufficient if the operation is independent of external variables and only needs to be run a finite and relatively few number of times. The more operations required/likely to occur, the greater the strain on resources - just for calculating intervals. Another drawback takes effect when the processing time exceeds the timeout interval, causing a knock-on to the interval of the observed redraws. The result of this can be that the web page freezes up entirely until all operations are done.
Example
for (var i=0, limit=n; i<limit; i++) {
setTimeout((function(params) {
return function() {
some_func(params);
}
})(param_values), i*1000);
}
The second method is a little more convoluted, but guarantees redraws between each operation, regardless of the timeout interval. Here, the timeout only affects the time between redraws ands resists the effects of consecutive operation variables. However, the processing time for the current operation is still a factor for the observed interval and will still freeze up a web page between redraws if the operation is computationally intensive.
Example
var limit = n;
var i = 0;
recursive_timeout();
function recursive_timeout() {
setTimeout((function(params) {
return function() {
some_func(params);
i++;
if (i<limit) {
recursive_timeout();
}
}
})(param_values, i, limit), 1000);
}
Refined Example (based off guest271314's answer)
var still_true = true;
recursive_timeout();
function recursive_timeout() {
some_func(params);
if (still_true) {
setTimeout(function() {recursive_timeout();}, 1000);
}
}
While the incremental method is fine for simple tasks, recursion will reliably perform redraws. If long processing times per operation is an issue, then it might be worth delving into asynchronous tasks in addition to using recursion in order to avoid rendering a web page temporarily unusable.
Anyway, hope this helps!
Ha! Just realised guest271314 put up a much more elegant example of the recursive method... Oh well, more info can't hurt.

How do you slow down the execution of a for loop in JavaScript?

I am making a basic image slider (press the next/prev button and the picture changes). I am also trying to implement a very simple fade effect; to do this I have a for loop which changes the class of the image, cycling through classes that change the opacity. The code works fine and does what I want it to do, but the for loop executes so quickly that you don't notice the change in opacity. I have looked all over and everywhere people are mentioning setTimeout, but that only causes a delayed start of a function.. I just want to slow my for loop so you can visually notice each iteration.
function nextphoto(){
for(var x = 0; x < 5; x++){
photo.className = fade[x];
}
i++;
if(i>19){
i=0;
}
photo.src = image[i];
for(var y = 4; y >= 0; y--){
photo.className = fade[y];
}
}
You might be misunderstanding the concept of synchronous code execution. All of your code has to finish running before anything can be updated or "rendered" to the screen. So your for loop will run until it's done, then the screen will update, but it will of course only have the final view of the image to render.
You should either trigger a CSS transform, by dynamically adding a class to the element via javascript, or if you were desperate to do it all in code, had some reason not to use CSS, and do not want to write a custom per frame animation system, or use a library, then inside each iteration of the for loop, fire an asynchronous function that will update at a later and later time, outside of the current synchronous for loop code.
ie:
var aThing;
for(var i = 0; i < someNumber; i++){
setTimeout(function(){
aThing.someValue = aThing.someValue * .9;
}, 500 * i);
}
You should consider using JQuery's animate() function to animate css values like opacity. Even better you could be using CSS animations.
What you're doing is NOT a good way to achieve your goal. If you really want it that way you should use setInterval or setTimeout.

Why is this for loop blocking if it is called afterwards?

Why does the div[id=box] not get updated until the for loop finishes? If I comment out the for loop, the div displays instantly.
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// loop after element update
for (var i = 0; i < 2000000000; ++i) {}
};
http://jsfiddle.net/472BU/
Simply, ALL browser processes (JS, repainting the page, even responding to user-clicks/key-presses and in most cases refreshes page-changes... even closing the tab) all happen in the same process thread.
Thankfully this isn't 100% true, 100% of the time, anymore.
Certain browser-vendors are working to move different parts of the web-platform to different threads, for a smoother experience, but typically, if you lock your JS up, you lock everything.
This simply means that the browser won't actually repaint until JS has finished running, and gives control back to the DOM.
The good news is that it means you can measure elements by unhiding them, grabbing their dimensions and hiding them again, at the end of the function. The width/height that they would take up is calculated on the spot, but a large portion of the page might have to be painted if you change an element, so if it's possible to change 30000 elements in a loop, then painting them all as it happens would be a very bad thing.
The cause is already explained by others. If you want the box to be painted instantly, the solution is simple. Put the loop in a timeout:
document.getElementById('click').onclick = function() {
document.getElementById('box').style.display = 'block';
// no delay anymore
setTimeout( function(){for (var i = 0; i < 2000000000; ++i) {}},10);
};
jsFiddle
Also check web workers
That amount of iterations running continuously will use up all of the browser's resources and it won't be able to worry with applying styles.
Your javascript is executed in the order it appears there, but behind the scenes there is a queue for rendering style changes. In any normal usage, you wouldn't notice this behavior, but since you're running an poor performant loop, it becomes evident.
Problem
It's because JavaScript is single-threaded and will only be able to run that loop.
Anything else will be on hold for as long as the loop lasts. As the DOM is wired into the JavaScript the DOM will be blocked as well (in general, except in browsers where DOM runs on a separate thread and will generate an event for the event queue instead which will be on hold until the current executing scope has finished).
Solution
To avoid this you need to split your functions into several asynchronous operations (not the same as multi-threaded) which will enable the browser to invoke some of the events queued up in the event queue (for example paint events).
You can do this by splitting up your function to perform iteration in segments using an inner mechanism to dispatch batches instead.
For example:
Live demo
function busyLoop(callback) {
var segCounter = 0, /// keep track of segment
totCounter = 0, /// keep track of total count
max = 2000000000, /// max count
segment = 1000000; /// segment size (smaller = better response)
/// invoke first batch
(function nextBatch() {
segCounter = 0; /// reset segment counter for each time
for(; segCounter < segment && totCounter <= max; segCounter++, totCounter++) {
///...work here...
}
if (totCounter < max) {
/// call setTimeout() which makes it async, +/- 11ms gives browser
/// chance to process other events such as paint events:
setTimeout(nextBatch, 11);
/// optional progress callback here
} else
callback();
})();
}
Then call it with a callback function:
busyLoop(doneFunction);
Notice that you can now interact with DOM as well as getting feedback.
Tip: The smaller segments the more responsive the DOM but the longer the total time as the delay in-between accumulates. Experiment to find a balance that suits your solution.
Hope this helps.

How to force a reflow in javascript in webkit 1.2.5

I've tried everything that is supposed to invoke a reflow but that isn't happening. I'm calling my test function 10 times to draw some element on my screen and I move that element through each iteration. That loop is executed immediately and in the end I get one picture instead of seeing the movement of the element on the screen.
It's as if when all work is done, reflow and drawing on the screen is invoked. But i want to see each drawing.
All the things I've tried didn't give any results. The only thing that works is alert(), but i don't need an interaction with user.
I'm using an webkit 1.2.5 if that helps.
If I'm not understandable enough I will try to explain better.
This the code I'm forcing to reflow
var i = 0;
for(;i<500;i+=50){
fTestInfo(i);
console.log("Test loop!!! "+i);
}
The thing I nedd is to see a picture on my screen each time fTestInfo(i) is executed but instead, i only see the ending result.
fTestInfo depends on i it moves in left by the value of i.
I see you are using a for loop which typically means you misunderstand how timers work. The for loop is synchronously executed and you are probably setting all the timers at once.
Try this:
(function loop(i) {
if (i >= 500) {
return;
}
document.querySelector("div").style.left = i + "px";
setTimeout(function() {
loop(i + 1);
}, 16);
})(0);
​
demo http://jsfiddle.net/UCfmF/
I suppose you mean getting a value like .offsetWidth? This is not guaranteed to make a visible reflow on the screen, browsers may wait for some time (read: until javascript execution stops) before actually attempting to paint anything on the screen even if you are doing actions that trigger reflows.
This means that if you append 1000 elements to the document, it will not trigger 1000 reflows. Even if you fetch .offsetWidth in between each iteration. It will just be calculated for you but not necessarily painted.
You need to move the elements with a timer as the end of javascript execution is when browsers flush out any queued reflows.
See http://dev.opera.com/articles/view/efficient-javascript/?page=3#reflow
As stated earlier, the browser may cache several changes for you, and
reflow only once when those changes have all been made. However, note
that taking measurements of the element will force it to reflow, so
that the measurements will be correct. The changes may or may not not
be visibly repainted, but the reflow itself still has to happen behind
the scenes.
You need to give the browser the opportunity to enter its event loop between each iteration.
Use setTimeout to schedule each iteration of the drawing:
function scheduledTestInfo(i) {
setTimeout(function() {
fTestInfo(i);
}, i); // calls each function 50ms apart
}
var i = 0;
for ( ; i < 500 ; i += 50) {
scheduledTestInfo(i);
}

Counting down for x to 0 in Javascript?

I have from the backend a time on the format 00:12:54 and I display it to the screen. But, I would like to have this time to continue to go down. I have though to create a variable in javascript that will old the time and with setTimeout to loop to display with document.getElementById the new value. I think it can be problematic if I have many time to go down in same time. I might require an array?
How would you do that? If I have no other suggestion, I will try my way, but I am curious to know if it does have a more secure way to do it.
Do you know jQuery Framework? It's a Javascript framework that have a lot of utilities methods and functions that let you do Javascript stuff more easily.
Here is a count down plugin (haven't tested it).
I suggest you to download JQuery than download the plugin . Check the sample of code from the "relative" tab on the website. You can have something like :
$('#until2d4h').countdown({until: '+12M +54S'});
*The only drawback with what I suggest you is that you will require 2 .js to be added. Try to add them only when needed and you will be find.
General algorithm:
Read time from server.
Read the current time.
Call a function.
In your function, read the current time, get the delta from the initial time you read in step 2.
Subtract the delta from the initial time you read from the server in step 1 and display the remainder.
The function should call window.setTimeout to call itself in 1000ms (or adjust according to time elapsed within the function), if you want to continue counting down.
Here's a rough cut:
window.onload = function () {
var countdown_start_in_ms = 6000; // from server
function tick() {
var now = new Date().getTime();
var disp = start - now;
if (disp < 0) {
disp = 0;
}
var el = document.getElementById("countdown");
el.innerHTML =
// quick hack to format time
/(\d\d:\d\d:\d\d) ...$/.exec(new Date(disp).toUTCString())[1];
if (disp > 1000) {
var elapsed = new Date().getTime() - now;
window.setTimeout(tick, 1000 - elapsed);
} else {
// stop countdown and set color to light grey
el.style.color = "#ccc";
}
}
var start = new Date().getTime() + countdown_start_in_ms;
tick();
}
You won't like the taste of this one, but it'll do you good:
Google for 'javascript timer' and get your hands dirty reading through the various examples and tutorials returned by that search.
You'll learn a lot more than just how to write a count-down timer. :-)
Good luck!
Take a look at Grab hands and set your own time. and inspect its code. While it is written with Dojo, the "clock" part is in plain JavaScript. In your case the only difference is how to advance the counter — decrease rather than increase it.

Categories

Resources