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.
Related
I need to run the below code and after 10 seconds the SetInteraval function to be stopped but in the same time to assure that the full word has been executed.
The code I had written:
var word = "I love JS More than any Programming Language in the world!";
var counter = 0;
var autoTyping = setInterval(function() {
var h3 = document.getElementById("myh3");
h3.innerText = word.substring(0, counter);
counter++;
if (counter > word.length) {
counter = 0;
}
}, 100);
setTimeout(function() {
clearInterval(autoTyping);
}, 5000);
So I need after 5 seconds this code stop and this happened but it can be stopped without ensuring that full word "Variable word" has been totally completed written on the DOM.
I assume that you want to print the word variable to h3 element, per letter, and stop it after 5s AND the variable was fully-typed.
Here's my solution with recursive approach:
[UPDATE]
Added typing loop with timeout stopper
// word to type
var _word = "I love JS More than any Programming Language in the world!"
// target element's id
var _target = 'myh3'
// time to fully-typed the word
var _time = 5000 // ms
// speed is depend on _time and _word's length
var _speed = _time/_word.length
// your auto-typing stopper
var _timeout = 10000 // ms
// auto-typing function
function autoType (word, target, speed, timeout) {
var element = document.getElementById(target)
var counter = 0
var stopped = false
function typing(){
if(counter < word.length){
element.innerHTML += word[counter]
counter++
setTimeout(function(){
// recursive call
typing()
}, speed)
}else{
// check if you want it to stop
if(stopped === false){
// ok. you don't want it to stop now. reset counter
counter = 0
// reset the element if you want it too
element.innerHTML = ''
// start it again
typing()
}else{
// console.log('auto-typing is done')
}
}
}
// timeout is required. you dont want a infinite loop, right?
if(timeout){
typing()
setTimeout(function(){
stopped = true
}, timeout)
}
}
// execute it
autoType(_word, _target, _speed, _timeout)
body {background: white}
<h3 id="myh3"></h3>
Well, you are almost there. In your setInterval callback add a line where you clear the interval whenever the word length is reached.
In the setTimeout callback first check if the innerText value of your element is equal to the word. This way you can see if the full sentence has been printed out and only stop if it is so. Otherwise the setInterval will just continue to run until the word length is reached.
var h3 = document.getElementById("myh3");
var word = "I love JS More than any Programming Language in the world!";
var counter = 0;
var autoTyping = setInterval(function() {
h3.innerText = word.substring(0, counter);
counter++;
if (counter >= word.length) {
clearInterval(autoTyping);
counter = 0;
}
}, 100);
setTimeout(function() {
if (h3.innerText === word) {
clearInterval(autoTyping);
}
}, 5000);
you can just have the clearinterval in your if statement, without the need to have setTimeout function:
var word = "I love JS More than any Programming Language in the world!";
var counter = 0;
var h3 = document.getElementById("myh3");
var autoTyping = setInterval(function() {
h3.innerText = word.substring(0, counter);
counter++;
if (counter > word.length) {
counter = 0;
//clearInterval(autoTyping);
}
}, 100);
setTimeout(function() {
if(h3.innerHTML !== word) {
h3.innerHTML = word;
}
clearInterval(autoTyping);
}, 10000);
<div id="myh3">
</div>
I really like to use ES6 generator functions, when it comes to intervals. They make the code much cleaner.
Here's an example of a reusable typewriter function, that takes the element, the word and the interval; and returns a stop function:
function typewriter(element, word, interval){
let stopped = false
const iterator = (function*() {
//This try..finally is not necessary, but ensures that typewriter stops if an error occurs
try{
while(!stopped){
for(let i=0; i<word.length; i++){
element.innerText = word.substring(0, i);
yield
}
}
}finally{
clearTimeout(autoTyping)
}
})()
const autoTyping = setInterval(() => iterator.next(), interval);
iterator.next()
return function stop(){
stopped = true
}
}
const word = "I love JS More than any Programming Language in the world!";
const h3 = document.getElementById("myh3");
const stop1 = typewriter(h3, word, 100)
setTimeout(stop1, 10000)
const secondh3 = document.getElementById("my2ndh3");
const stop2 = typewriter(secondh3, word, 100)
//Even if stopped immediately, it types the full sentence
stop2()
<h3 id="myh3"></h3>
<h3 id="my2ndh3"></h3>
To return results, you can even promisify this function, that has the advantage of having an exception channel for asynchronous errors:
function typewriter(element, word, interval){
let stopped = false
const promise = new Promise((resolve, reject) => {
const iterator = (function*() {
try{
while(!stopped){
for(let i=0; i<word.length; i++){
element.innerText = word.substring(0, i);
yield
}
}
resolve()
}catch(e){
reject(e)
}finally{
clearTimeout(autoTyping)
}
})()
const autoTyping = setInterval(() => iterator.next(), interval);
iterator.next()
})
promise.stop = function stop(){
stopped = true
}
return promise
}
const word = "I love JS More than any Programming Language in the world!";
const h3 = document.getElementById("myh3");
const promise1 = typewriter(h3, word, 100)
promise1.then(() => console.log('1st example done'))
setTimeout(promise1.stop, 10000)
typewriter(null, word, 100) //Cause error with null element
.catch(e => console.error('2nd example failed:', e.stack))
<h3 id="myh3"></h3>
I'm trying to write a function that takes a large array and iterates up and down it in a set number of chunks via a previous and next button. I have the next button working fine but cannot get it to reverse the array the same way I go forward. Here's what I have:
Javscript
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var next = true;
var increment = function(array,next) {
if (array.chunk < array.length) {
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
if (next) {
array.chunk += array.chunkSize;
$( '#test' ).html('<p>' + chunk + '</p>');
}
else {
var slice = array.slice(
array.chunk,
Math.min(array.chunk+array.chunkSize, array.length));
array.chunk -= array.chunkSize;
$( '#test' ).html(chunk);
}
}
}
$("#prev").click(function() {
increment(text);
});
$("#button").click(function() {
increment(text, next);
});
}
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var increment = function(array,next) {
if(next) {
array.chunk = Math.min(array.chunk + array.chunkSize, array.length);
} else {
array.chunk = Math.max(array.chunk - array.chunkSize, 0);
}
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
}
$("#prev").click(increment(text,false));
$("#button").click(increment(text, true));
}
Is this what you need? Fastly coded, and without testing so use with caution.
Okay, so first of all I really suggest breaking up your code. It looks like this is a response back from a server. I would in the response from the server, parse the data just like you are (side note, why don't you just return json from the server?) but use a call back to handle pagination.
var returnData = data.split(' ');
addPagination(returnData);
Under addPagination I would handle the DOM manipulation:
function addPagination(array) {
$('#container').show();
var incremental = incrementArray(array);
var responseSpan = $('#response');
$('#previous').click(function() {
incremental.previous();
showText();
});
$('#next').click(function() {
incremental.next();
showText();
});
showText();
function showText() {
responseSpan.text(incremental.array.join(', '));
}
}
But to handle the actual shifting of the array, I would use some function outside of your own. This uses Object Oriented JavaScript, so it is a bit more complex. It stores the original array in memory, and has 2 methods (next, previous), and has 1 attribute (array):
function incrementArray(array) {
_increment = 2;
var increment = _increment < array.length ? _increment : array.length;
var index = -2;
this.next = function () {
var isTopOfArray = index + increment > array.length - increment;
index = isTopOfArray ? array.length - increment : index + increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.previous = function () {
var isBottomOfArray = index - increment < 0;
index = isBottomOfArray ? 0 : index - increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.next();
return this;
}
To test it, use this jsFiddle.
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);
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]);
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/