User interaction conflict during SetTimeout in Javascript - javascript

I am working on a WordPress (Javascript) plugin that alters text fields based on user interaction with an HTML5 slider. One of its effects is to reveal a <span> string one character at a time using SetTimeout to create a delay (a few ms) so the effect is perceptible. I'm accomplishing this by getting the DOM element's contents and then rebuilding it one character at a time.
The problem is that since SetTimeout is aynsynchronous, the user can potentially move the slider faster than a single reveal loop can complete, resulting in half-empty DOM elements that never get corrected.
Is there a way to prevent this, or alternatively, a way to accomplish the task that avoids the conflict altogether? I have tried turning off the EventListener (for the HMTL5) at various points in the delay loop but cannot find a place that avoids the issue. The other possibility is to load all the <span> contents into arrays in order to retain intact copies of everything ... but something tells me there's a better way to do it that I don't know.
Here is example code. Initialize() is called when the HTML page involved loads.
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if (
// conditions
)
{ var cols = document.getElementsByClassName(attr+i);
RevealTextLines (cols);
}
// 'delay' is a global variable to set the delay length
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h = h + numchars;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
}, delay);
}
})(1);
});
}

The boolean flag suggested above does not work in this case, but it did inspire the following solution:
Provided the number of iterations are known in advance (which in this case they are), define a global counter variable outside the functions. Before the SetTimeout loop, set it to the number of iterations and decrease it by 1 every time through. Then have the calling function proceed only when the counter's value is zero.
var counter = 0;
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if ( counter == 0)
{ var cols = document.getElementsByClassName(classname);
RevealTextLines (cols);
}
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
timer = el.length;
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h++;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
timer--;
UpdateSlider();
}, delay);
}
})(1);
});
}
If anyone knows a more efficient solution, I would remain interested.

Related

How to use timer in d3 V3?

I have a function triggerWave() which makes the points on the canvas animate in the wave form. I am using d3.ease('quad-in') for easing and I would like to use d3.timer() to make the triggerWave() function call over 200ms timeframe. I am out of luck in finding the tutorials or examples on d3.timer.
triggerWave() {
//function logic
let count = 0;
let xScale = d3.scale.linear().range([1,2]); // want the value to change from 1 to 2.
let zScale = d3.scale.linear().domain([0, 200]); // 200 ms.
let value = xScale(d3.ease('quad-in')(zScale(count)));
if(count < 200){
count++;
d3.timer(() => triggerWave());
} else {
// do something
}
this.wave.next({currentFrame: value});
}
When I call d3.timer() as above, the triggerWave() function gets called infinite times and never stops. I want to manipulate or control the time. In my case, I want the timer() to be triggered for 200ms.
How can I understand how to use the d3.timer() function?
(EDIT: I totally and completely missed the huge, big "V3" which is right there, in the title of the question. Sorry. I'll keep this answer here as reference for v4 users)
Since you are calling triggerWave inside the triggerWave function itself, you don't need d3.timer, but d3.timeout instead. According to the API, d3.timeout:
Like timer, except the timer automatically stops on its first callback. A suitable replacement for setTimeout that is guaranteed to not run in the background. The callback is passed the elapsed time.
Also, pay attention to the fact that you are reseting count every time the function runs, which will not work. Set its initial value outside the function.
Here is a demo with those changes. I'm calling the function every 200 ms, until count gets to 50:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
You can also keep track of the total elapsed time, using the argument passed to triggerWave by d3.timeout:
var p = d3.select("p")
var count = 0;
var elapsed = 0;
var format = d3.format(".2")
triggerWave();
function triggerWave(t) {
elapsed = t ? elapsed + t : elapsed;
p.html("Count is " + count + ", and the elapsed time is " + format(elapsed/1000) + " seconds")
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
Since you are using D3 v3, and as there is no d3.timeout in v3, you can do the same approach using vanilla JavaScript: setTimeout.
Here is a demo:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
setTimeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>
In version 3, there is no d3.timer.stop() function. You have to return true after a certain period of time to stop the timer.
In Gerardo's answer, he explained fabulously how to use the d3 timeout which would be a valid solution to your problem i.e., to call the function over and over for a certain period of time. But looking at your comments on Gerardo's answer, I think you are looking for something else.
Here's what I came up with and I think this is what you are looking for:
You can create an another function called as activateTriggerWave() which will be invoked on the button click and inside this function, you can call your triggerWave() method using the d3 timer.
function activateTriggerWave() {
d3.timer(elapsed => {
this.triggerWave();
if(elapsed >= 200){
return true; // this will stop the d3 timer.
}
});
}
triggerWave() {
// here you can do whatever logic you want to implement.
}
I hope this helps.
I use d3.js v3, and the timer can be stopped by any user action. In the d3.js docs it is shown to use it as:
d3.timer(function(elapsed) {
console.log(elapsed);
return elapsed >= 1000;
});
I have few examples in which the animation is forever and there is no reason to set a limit on it. Checking the standalone d3.timer which comes with a stop(), I found that it behaves quite slow comparing it with the default timer included in the v3 toolset, probably for some version incompatibility.
The solution is use is to:
var timer_1_stop=false;
set it as global var accesible from the page scope. Then the timer:
const run=function(){
//...
d3.timer(function() {
voronoi = d3.geom.voronoi(points).map(function(cell) { return bounds.clip(cell); });
path.attr("d", function(point, i) { return line(resample(voronoi[i])); });
return timer_1_stop;
});
}
const stopVoro=function(){
timer_1_stop=true;
}
It allows to do:
class Menu extends React.Component {
render(){
return(<ul>
<li><span onClick={()=>{stopVoro()}}>StopVoro</span></li>
</ul>)
}
}

Worn out getting animation to sequence

This is originally from (Pause execution in while loop locks browser (updated with fiddles))
I have been at this all day and I can't figure out how to keep javascript from advancing to the next line and in essence executing all lines at once. I have tried every combination of delay / setTimeout I can think of to no avail.
I just want the elements in the array to flash once then pause, then do it again for another element in the array till all elements have been removed and the array is empty.
But because javascript is executing all lines at once I end up with the appearance of all elements flashing at the same time.
Here is the fiddle:
http://jsfiddle.net/ramjet/xgz52/7/
and the relevant code:
FlashElement: function () {
while (elementArray.length) {
alert('a ' + elementArray.length);
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
alert('delay complete');
elementArray = elementArray.not($el);
alert('array popped');
alert('z ' + elementArray.length);
}
},
ANSWER FOR THIS SITUATION. Hopefully it will help others.
As Zach Saucier points out the loop was really my problem...but not the only problem. I was the other problem(s).
Me first.
Fool that I am I was really causing my own complications with two things I was doing wrong.
First using jsfiddle my javascript would error due to syntax or some such thing but fiddle doesn't tell you that (to my knowledge) so my fiddle wouldn't run but I took it in pride as MY CODE IS FINE stupid javascript isn't working.
Second I was passing my function to setTimeout incorrectly. I was adding the function parens () and that is not correct either which would bring me back to issue one above.
WRONG: intervalTimer = setInterval(MyFunction(), 1500);
RIGHT: intervalTimer = setInterval(MyFunction, 1500);
As for the code. As Zach pointed out and I read here (http://javascript.info/tutorial/settimeout-setinterval) while he was responding setting a timeout in a loop is bad. The loop will iterate rapidly and with the timeout one of the steps in the loop we get into a circular firing squad.
Here is my implementation:
I created a couple variables but didn't want them polluting the global scope so I created them within the custom domain. One to hold the array of elements the other the handle to the setInterval object.
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
....
}
In my onReady function (the one the page calls to kick things off) I set my domain array variable and set the interval saving the handle for use later. Note that the interval timer is how long I want between images flashes.
onReady: function ()
{
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
},
Now instead of looping through the array I am executing a function at certain intervals and just tracking how many elements are left in the array to be flashed. Once there are zero elements in the array I kill the interval execution.
FlashElement: function ()
{
if(elementArray.length > 0) //check how many elements left to be flashed
{
var $el = PageLoadAnimation.GrabElement(); //get random element
PageLoadAnimation.FlashBlast($el); //flash it
PageLoadAnimation.RemoveElement($el); //remove that element
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
So the whole thing is:
var PageLoadAnimation =
{
elementArray: null,
intervalTimer: null,
onReady: function () {
elementArray = $('#PartialsContainer').children();
//black everything out just to be sure
PageLoadAnimation.BlackOutElements();
//flash & show
intervalTimer = setInterval(PageLoadAnimation.FlashElement, 1500);
//NOT this PageLoadAnimation.FlashElement()
},
BlackOutElements: function () {
$('#PartialsContainer').children().hide();
},
FlashElement: function ()
{
if(elementArray.length > 0)
{
var $el = PageLoadAnimation.GrabElement();
PageLoadAnimation.FlashBlast($el);
PageLoadAnimation.RemoveElement($el);
}
else
{
//done clear timer
clearInterval(intervalTimer);
intervalTimer = null;
}
},
GrabElement: function()
{
return elementArray.eq(Math.floor(Math.random() * elementArray.length));
},
RemoveElement: function($el)
{ elementArray = elementArray.not($el); },
FlashBlast: function ($el) {
//flash background
$el.fadeIn(100, function () { $el.fadeOut(100) });
}
}
Hope that help others understand the way to go about pausing execution in javascript.
The reason why you were having trouble is because setTimeout function is non-blocking and will return immediately. Therefore the loop will iterate very quickly, initiating each of the timeouts within milliseconds of each other instead of including the previous one's delay
As a result, you need to create a custom function that will wait on the setInterval to finish before running again
FlashElement: function () { // Call it where you had the function originally
myLoop();
},
...
function myLoop() {
setTimeout(function () { // call a setTimeout when the loop is called
var $el = elementArray.eq(Math.floor(Math.random() * elementArray.length));
PageLoadAnimation.FlashBlast($el);
elementArray = elementArray.not($el);
if (0 < elementArray.length) { // if the counter < length, call the loop function
myLoop();
}
}, 1000)
}
Feel free to change the delay to whatever value you wish (3000ms to let each fade finish before the last at the moment). If you want to start the fade in of the next before the previous ends and keep them in their original positions you would have to animate the opacity using .css instead of using fadeIn and fadeOut
My answer is based on this answer from another SO question

clearInterval doesnt stop loop

I have the following code
startProgressTimer: function () {
var me = this,
updateProgressBars = function (eventItems) {
alert("updateProgressBars: looping");
alert("me.eventProgressTimerId:" + me.eventProgressTimerId);
var i = 0;
if (eventItems.length === 0) {
alert("internal Stop Begin")
clearInterval(me.eventProgressTimerId);
alert("internal Stop End")
eventItems = [];
}
for (i = 0; i < eventItems.length; i++) {
if (eventItems[i]._eventId) {
eventItems[i].updateProgressBar();
}
}
};
alert("Start Progress Timer");
this.eventProgressTimerId = setInterval(function () {
updateProgressBars([]);
}, 10000);
}
When the function is called I would expect it to run and bottom out only it keeps on looping.
screen output
ALERT:updateProgressBars: looping
ALERT:me.eventProgressTimerId:10
ALERT:internal Stop Begin
ALERT:internal Stop End
ALERT:updateProgressBars: looping
ALERT:me.eventProgressTimerId:10
ALERT:internal Stop Begin
ALERT:internal Stop End
Any ideas
I suspect the problem might be that the code you don't show calls the startProgressTimer() method more than once for the same instance of whatever object it belongs to, and then within the method you store the interval id in an instance property this.eventProgressTimerId - so multiple calls overwrite the property and you'd only be able to cancel the last one.
If that's the case, a simple fix is to declare your eventProgressTimerId as a local variable within startProgressTimer().

How to update a web page from javascript without exiting javascript

I would like to animate an html page with something like this:
function showElements(a) {
for (i=1; i<=a; i++) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
pause(500);
}
}
function pause(ms) {
ms += new Date().getTime();
while (new Date() < ms){}
}
Unfortunately, the page only renders once javascript completes.
If I add
window.location.reload();
after each pause(500); invocation, this seems to force my javascript to exit. (At least, I do not reach the next line of code in my javascript.)
If I insert
var answer=prompt("hello");
after each pause(500), this does exactly what I want (i.e. update of the page) except for the fact that I don't want an annoying prompt because I don't actually need any user input.
So... is there something I can invoke after my pause that forces a refresh of the page, does not request any input from the user, and allows my script to continue?
While the javascript thread is running, the rendering thread will not update the page. You need to use setTimeout.
Rather than creating a second function, or exposing i to external code, you can implement this using an inner function with a closure on a and i:
function showElements(a) {
var i = 1;
function showNext() {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
i++;
if(i <= a) setTimeout(showNext, 500);
}
showNext();
}
If I add window.location.reload(); after each pause(500) invocation, this seems to force my javascript to exit
window.reload() makes the browser discard the current page and reload it from the server, hence your javascript stopping.
If I insert var answer=prompt("hello"); after each pause(500), this does exactly what I want.
prompt, alert, and confirm are pretty much the only things that can actually pause the javascript thread. In some browsers, even these still block the UI thread.
Your pause() function sleeps on the UI thread and freezes the browser.
This is your problem.
Instead, you need to call setTimeout to call a function later.
Javascript is inherently event-driven/non-blocking (this is one of the great things about javascript/Node.js). Trying to circumvent a built in feature is never a good idea. In order to do what you want, you need to schedule your events. One way to do this is to use setTimeout and simple recursion.
function showElements(a) {
showElement(1,a);
}
function showElement(i, max) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < max) {
setTimeout(function() { showElement(i+1, max) }, 500);
}
}
var i = 1;
function showElements(a) {
var img = document.getElementById(getImageId(i));
img.style.visibility = 'visible';
if (i < a) {
setTimeout(function() { showElements(a) }, 500);
}
i++;
}
showElements(5);
function showElements(a,t) {
for (var i=1; i<=a; i++) {
(function(a,b){setTimeout(function(){
document.getElementById(getImageId(a)).style.visibility = 'visible'},a*b);}
)(i,t)
}
}
The t-argument is the delay, e.g. 500
Demo: http://jsfiddle.net/doktormolle/nLrps/

setInterval to loop through array in javascript?

I have a website where they want a news ticker. Currently, I have a array that populates it, and every x seconds, I want the news story to change.
function startNews(stories) {
}
I am aware that you can use setInterval, but it has to go through a new function and you can't specify certain javascript in the same function to fire when it does.
What are you suggestions?
Thanks!
You should use either setInterval() or repeated calls to setTimeout(). That's how you do something in javascript at some time in the future.
There are no limitations on what you can do with either of those timer functions. What exactly do you think you cannot do that is making you try to avoid them?
Here's a pseudo code example:
var newsArray = []; // your code puts strings into this array
var curNewsIndex = -1;
var intervalID = setInterval(function() {
++curNewsIndex;
if (curNewsIndex >= newsArray.length) {
curNewsIndex = 0;
}
setTickerNews(newsArray[curNewsIndex]); // set new news item into the ticker
}, 5000);
or it could be done like this:
var newsArray = []; // your code puts strings into this array
var curNewsIndex = -1;
function advanceNewsItem() {
++curNewsIndex;
if (curNewsIndex >= newsArray.length) {
curNewsIndex = 0;
}
setTickerNews(newsArray[curNewsIndex]); // set new news item into the ticker
}
var intervalID = setInterval(advanceNewsItem, 5000);
You should whenever possible use setTimeout. If your function takes longer to run than the interval, you can run into a constant 100% cpu usage situation.
Try this code:
http://jsfiddle.net/wdARC/
var stories = ['Story1','Story2','Story3'],
i = -1;
(function f(){
i = (i + 1) % stories.length;
document.write(stories[ i ] + '<br/>');
setTimeout(f, 5000);
})();
Replace document.write with your function.

Categories