Consider the following code:
function func() {
var totalWidths = 0;
for( var i = 0, count = arr.length; i < count; i++ ) {
var image = arr[i];
insertElemInDOM(image);
preloadImage(image,function(){
var w = image.width();
totalWidths += w;
});
}
// do something with the variable "totalWidths"
doSomething(totalWidths)
}
I have 2 problems here. The image will be always the same (first problem), which one can solve with an anonymous function:
for(...) {
(function(image) {
preload(image,function() {
// now image is the correct one
});
})(image);
}
But how do I manage the totalWidths variable in order to use it later on doSomething(totalWidths)? The previous code would have a value of 0 for totalWidths.
Thanks!
You could timeout the whole loop and the doSomething, that's much more performant than setting up so many timeouts:
setTimeout(function() {
var inc = 0;
for (var i = 0; i < count; i++) {
var w = arr[i].width();
inc++;
}
doSomething(inc);
}, 1000);
However, what you actually seem to want are nested timeouts, i.e. waiting 1s for each iteration step and doing something after all have finished:
var inc = 0, count;
function asyncLoop(i, callback) {
if (i < count) {
var w = arr[i].width();
inc++;
setTimeout(function() {
asyncLoop(i+1, callback);
}, 1000);
} else {
callback();
}
}
asyncLoop(0, function() {
doSomething(inc);
});
OK, now that we know what you need the solution is to check after each load event whether all images are loaded:
var totalWidths = 0,
count = arr.length,
loaded = 0;
for (var i = 0; i < count; i++)
(function (image) {
insertElemInDOM(image);
preload(image, function() {
totalWidths += image.width();
// counter:
loaded++;
if (loaded == count-1) // the expected value
doSomething(totalWidths); // call back
});
})(arr[i]);
Related
I'm trying to add a string to a html element every 500ms using a for loop to pass the string to a function which updates the target element.
I'm not sure if i'm approaching this the right way or if it's possible as it just displays the strings all at once rather than every 500ms.
The desired effect is the strings displays as if someone is typing.
The code is below and here is a jsFiddle.
var content = "Hello, Universe!";
var split = content.split("");
var target = document.getElementsByClassName('place-here');
for (i = 0; i < split.length; i++) {
addChar(split[i]);
}
function addChar(char) {
if (timer) {
clearTimeout(timer);
}
var timer = setTimeout(function() {
target[0].innerHTML += char;
}, 500);
}
Just a proposal without setTimeout, but with setInterval and some other changes.
var content = "Hello, Universe!",
target = document.getElementById('ticker'),
i = 0,
timer = setInterval(addChar, 500);
function addChar() {
if (i < content.length) {
target.innerHTML += content[i];
i++;
} else {
clearTimeout(timer);
}
}
<div id="ticker"></div>
The problem is that all timeout functions starts at the same time (in 500ms). You can compute different timeouts by multiplying interval with the index of char in array:
for (i = 0; i < split.length; i++) {
addChar(split[i], i);
}
function addChar(char, i) {
setTimeout(function () {
target[0].innerHTML += char;
}, 500 * i);
}
You can mix setInterval and shift to do this :
var target = document.getElementsByClassName('place-here');
function display(element, string, timer) {
var split = string.split("");
var interval = setInterval(function() {
element.innerHTML += split.shift()
if (split.length == 0) {
clearInterval(interval);
}
}, timer)
}
display(target[0], "Hello, Universe!", 500)
I made you a working javascript code that you can paste in your fiddle. Your problem was that you call the setTimetout all at the same time so after 500 milliseconds everything executes instead of waiting on the other one to finish
var content = "Hello, Universe!";
var split = content.split("");
var target = document.getElementsByClassName('place-here');
console.log(split);
addChar(0);
function addChar(i) {
var currentChar = split[i];
console.log(i);
console.log(currentChar);
setTimeout(function() {
if(typeof currentChar !== 'undefined'){
target[0].innerHTML += currentChar;
addChar(i+1);
}
}, 1000);
}
Why not using setInterval() :
here is a fiddle
code here :
var content = "Hello, Universe!";
var split = content.split("");
var target = document.getElementsByClassName('place-here');
var length = split.length;
var count = 0;
var timer = setInterval(function() {
addChar(split[count]);
count++;
if(count>=length) clearInterval(timer);
}, 500);
function addChar(char) {
target[0].innerHTML += char;
}
<div class="place-here">
</div>
I made a few changes in your jsfiddle: https://jsfiddle.net/vochxa3f/10/
var content = "Hello, Universe!";
var split = content.split("");
var target = document.getElementsByClassName('place-here');
function addChar(str, i) {
if(i < str.length) {
target[0].innerHTML += str[i];
setTimeout(addChar, 500, str, ++i);
}
}
setTimeout(addChar(content, 0), 500);
The addChar function call itself with setTimeout() incrementing the variable i so in the next time it'll get another character from content.
Note that setTimeout() first argument is the reference to the function, in this case only "addChar" without "()". The arguments for the function stars at 3rd parameter and forth.
I'm trying to use setTimeout with a lambda function in a for loop, but it only captures the last parameters for the contents of the lambda function from the final iteration of the for loop. In C# we can make a a new variable to pass as the parameter each time it is passed into a new lambda function, but that doesn't appear to work in javascript. Any clues?
The specific function I'm talking about is setElementsByIdTimed()
var gElems = new Array();
document.addEventListener("DOMContentLoaded", function (event) {
//setElementsById('icon_anim_start' , "icon_anim_end");
//setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , 500);
var delay = setElementsByIdTimed('icon_anim_start' , "icon_anim_end" , 250);
setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , delay);
});
function getElementsById (elementID){
var elementCollection = new Array();
var allElements = document.getElementsByTagName("*");
for(i = 0; i < allElements.length; i++){
if(allElements[i].id == elementID)
elementCollection.push(allElements[i]);
}
return elementCollection;
}
function setElementsById (elementID, newID) {
var elems = new Array();
elems = getElementsById(elementID);
for (var i = 0; i < elems.length; i++)
{
elems[i].id = newID;
}
}
function setElementsByIdTimed (elementID, newID , ms) {
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for (var i = 0; i < elems.length; i++) {
var index = i
setTimeout(function() {
setElementId(index, newID);
}, ms * i);
}
return ms * (elems.length-1);
}
function setElementId (index , newID) {
console.log ("gElems.length: " + gElems.length + " index: " + index);
gElems[index].id = newID;
}
})
This is a classic JavaScript closure problem. Basically, there is only one instance of the index variable and it's declared outside the context of the lambda function. So every lambda function is using the same index, and they are executed after the loop completes so index looks to be out-of-bounds on every invocation.
To get this to work index must have closure scope:
function setElementsByIdTimed(elementID, newID , ms)
{
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout( setClosure(index,newID), ms * i);
}
return ms * (elems.length-1);
}
function setClosure( index, newID ) {
// this lambda is called after the timeout elapses
return function() {
setElementId(index, newID);}
}
You can also play a self-invocation trick, but it's a little mind-bendy:
function setElementsByIdTimed(elementID, newID , ms)
{
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout( (function(idx,nid) {
return function () {
setElementId(idx,nid);}
})(index, newID),
ms * i);
}
return ms * (elems.length-1);
}
These are effectively the same solution, but the first syntax is probably a lot simpler to grasp.
instead of
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout(function() {
setElementId(index, newID);
}, ms * i);
}
use an IIFE - https://developer.mozilla.org/en-US/docs/Glossary/IIFE
for(var i = 0; i < elems.length; i++)
{
(function(index) {
setTimeout(function() {
setElementId(index, newID);
}, ms * index);
}(i));
}
the problem was in
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout(function() {
setElementId(index, newID);}, ms * i);
}
index is allways the same variable. Try:
for(var i = 0; i < elems.length; i++)
{
(function(index){
setTimeout(function() {
setElementId(index, newID);}, ms * i);
})(i);
}
each time you must access a variable within a clousure in a loop you must use a function to access it. You can also use a forEach applied to the array:
elems.forEach(function(index){
setTimeout(function() {
setElementId(index, newID);}, ms * i);
}
});
I am trying to make a javascript/jquery code in which it automatically types a string, and then after the string is completely typed, clears the previously typed string and types it again. Currently, all my code does is type the string once. I know how to keep looping it, but it just starts typing where it left off, and doesn't clear the string. Javascript:
var txt='Typing text...'.split('');
var delay=850;
for ( i=0; i<txt.length;i++){
setTimeout(function(){
$('.autoText').append(txt.shift() )
}, delay * i)
}
and here is my html:
<h1 class="autoText" style="font-size: 50px; color: #7CFC00; margin-top: 0em; margin-left: 25%;"></h1>
var txt = 'Typing text...'.split(''),
$h1 = $('.autoText'),
len = txt.length,
delay = 850,
i = 0;
setInterval(function() {
$h1.append(txt[i++]);
if (i > len) {
$h1.empty();
i = 0;
}
}, delay);
DEMO: http://jsfiddle.net/yt6hm4hc/
How about this:
var txt='Typing text...';
var delay=850;
var i = 0;
function type() {
if (i < txt.length) {
$('.autoText').append(txt[i]);
i++;
} else {
$('.autoText').text('');
i = 0;
}
setTimeout(type, delay);
}
type();
http://jsfiddle.net/16v15ufv/
Forgive the overkill, but if you make everything really generic then you can re-use bits in other places
A function to generate each char of a string, one per invocation
function stringGenerator(str) {
var i = 0;
return function () {
if (i < str.length)
return str.charAt(i++);
i = 0;
return null;
};
}
A function to loop using setTimeout, with a couple neat little tricks for ending the loop
function timeoutLoop(fn, freq, callback) {
function looper() {
var ret = fn();
if (ret !== false && ret !== null && ret !== undefined)
return window.setTimeout(looper, freq);
if (callback)
callback();
}
window.setTimeout(looper, freq)
}
A function which combines these with logic about text in the DOM, to produce your typing effect
function type(node, str, freq) {
var s = stringGenerator(str),
f = function () {
var chr = s();
if (chr === null)
return false;
if (node.lastChild.nodeType === 3)
node.lastChild.data += chr;
else
node.appendChild(document.createTextNode(chr));
return true;
};
timeoutLoop(f, freq);
}
Finally, invocation, e.g. to have the words Hello world! written to the <body> one character every 500 ms
type(document.body, 'Hello world!', 500);
This is 100% vanilla
Is this what you want?
DEMO
var txt = 'Typing text...'.split('');
var delay = 850;
function type() {
for (i = 0; i < txt.length; i++) {
setTimeout(function () {
$('.autoText').append(txt.shift());
}, delay * i);
}
// delay * i + 1000 means 1 second after the above text was finished typing
setTimeout(function(){
// after finish, clear text in h1 element
$('.autoText').text('');
// fire `type` method again, with `txt` reset to its original value
type(txt = 'Typing text...'.split(''));
}, delay * i + 1000);
}
type(); // call type at least once
var txt='Typing text...'.split('');
var delay=850;
for ( i=0; i<txt.length;i++){
setTimeout(function(){
$('.autoText').text('');
$('.autoText').append(txt.shift() )
}, delay * i)
}
You could use recursion:
var txt='Typing text...';
var delay=850;
function TypeText($el, txt, currentIndex, timeout) {
setTimeout(function() {
//If finished writing, restart at 0
if (currentIndex >= txt.length) {
TypeText($el, txt, 0, 1000);
return;
}
//If at 0, empty the text
if (currentIndex == 0) $el.empty();
//Append current letter
$el.append(txt[currentIndex]);
//Append next letter
TypeText($el, txt, currentIndex+1, timeout);
}, timeout);
}
TypeText($('.autoText'), txt, 0, delay)
fiddle here
Using setTimeout within a loop you'll create your own pain in the neck.
You could use recursion, something like this:
function repeatEternal(str, time){
time = time*1000 || 200;
var position = 0
,rdiv = document.querySelector('#repeater')
,l2r = true;
return eternal(str);
function eternal(s){
rdiv.textContent = l2r ? rdiv.textContent + s[0] : s.slice(0, -1);
l2r = s.length === 1 ? !l2r : l2r;
s = s.length < 2 ? str : !l2r ? s.slice(0,-1) : s.slice(1);
setTimeout( function(){ eternal(s); }, time);
}
}
// usage
repeatEternal('Typing galore!', 0.3);
Here's the jsFiddle
Here is the OOP solution,with Object literal way. also with method that you can change the Delay. and in the future you can add more methods like change case of string or change effects of animation with setters methods. you can append it to any element with any string.
also it's not have dependencies like a jQuery. and this code you must put in the bottom of the DOM if you don't want dependency of jQuery or inside document ready func and with that you have dependency .
Hope it's help you.
var animText = {
animStr: '',
delay: 300,
setDelay: function(delay) {
this.delay = delay;
},
DoAnimation: function(string, selector) {
this.animStr = string.split('');
for (var i = 0; i <= string.length-1; i++) {
setTimeout(function () {
document.getElementById(selector).innerHTML += animText.animStr.shift();
},this.delay * i);
};
setTimeout( function() {
document.getElementById(selector).innerHTML = '';
animText.DoAnimation(string, selector);
}, this.delay * i + 1200)
}
}
animText.DoAnimation("Hello", "animStr");
animText.setDelay(900);
There is a beautiful typewriter directive already written, but I'm looking for something more simple, that just adds each letter after an interval. I can't get this to work. It shows the text all at once. Something is wrong with the $timeout. Does anyone have any suggestions?
var time = 1000;
var addLetter = function(i) {
$scope.string2 = $scope.string.substr(0, i);
};
for (var i = 0, len = $scope.string.length; i < len; i++) {
(function(time) {
$timeout(function() {
addLetter(i);
}, (time + 300));
})(i);
}
Here is an alternate way.
var content = "contentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontent";
$scope.type = "";
var i=0;
var timer = $interval(function(){
if(i<content.length)
$scope.type += content[i];
else
$interval.cancel(timer);
i++;
$scope.$apply();
}, 100);
Credit: https://gist.github.com/frozonfreak/8018689
Demo: http://plnkr.co/edit/BILaWVuNpao2zcIInXLl?p=preview
Update - This is working for me, not sure if it could be more efficient...
var time = 0;
var addLetter = function(i) {
$scope.string2 = $scope.string.substr(0, i);
};
for (var i = 0, len = $scope.string.length; i < len + 1; i++) {
time = time + 50;
(function(time, i) {
$timeout(function() {
addLetter(i);
}, (time));
})(time, i);
}
This is my function, I wrote this for moving my div from left to right. After iter reach 5, the interval has to stop. But now it is keep on running. My interval is not clearing, what is wrong with my function?
var mydiv = document.getElementById('news-variety');
var iter = 0;
if (mydiv) {
var columns = mydiv.getElementsByTagName('DIV');
function animeColumn(index, col) {
var timerID = window.setInterval(function () {
var current = window.getComputedStyle(col);
var matrix = new WebKitCSSMatrix(current.webkitTransform);
col.style.webkitTransform = matrix.translate(10, 0);
if (iter++ == 5) {
window.clearInterval(timerID);
}
}, 50);
}
for (var i = 0; i < columns.length; i++) {
if (columns[i].className.toLowerCase() == "column") {
columns[i];
animeColumn(i, columns[i]);
}
}
}
There is a weird behavior (bug?) in firefox that requires you to specify the second parameter of getComputedStyle to 'null'. Try:
var current = window.getComputedStyle(col,null);
It is likely that in your current case, the code is throwing an 'argument count' error in the timeout handler, which prevents further execution and thus, never clears the timeout.
Alternatively, you may want to do your loop counting at the start of your loop, as such:
function animeColumn(index, col) {
var timerID = window.setInterval(function () {
if (iter++ == 5) {
window.clearInterval(timerID);
}
var current = window.getComputedStyle(col);
var matrix = new WebKitCSSMatrix(current.webkitTransform);
col.style.webkitTransform = matrix.translate(10, 0);
}, 50);
}
I think it is because your var timerID is not defined at window's level
var mydiv = document.getElementById('news-variety');
var iter = 0;
var timerID;
if (mydiv) {
var columns = mydiv.getElementsByTagName('DIV');
function animeColumn(index, col) {
timerID = window.setInterval(function () {
var current = window.getComputedStyle(col);
var matrix = new WebKitCSSMatrix(current.webkitTransform);
col.style.webkitTransform = matrix.translate(10, 0);
if (iter++ == 5) {
window.clearInterval(timerID);
}
}, 50);
}
for (var i = 0; i < columns.length; i++) {
if (columns[i].className.toLowerCase() == "column") {
columns[i];
animeColumn(i, columns[i]);
}
}
}
iter is global, so it will reach 5 for only one of multiple columns.
Try moving it into the function like this:
function animeColumn(index, col) {
var iter = 0;
var timerID = window.setInterval(function () {
// ...
if (iter++ == 5) {
window.clearInterval(timerID);
}
}, 50);
}
i have created a fiddle for this http://jsfiddle.net/DXSNp/