Reputation: 547
I have a jQuery function that uses setInterval()
to slowly type out a phrase--like with the phrase "string one", each character would be printed to the screen after a set number of milliseconds.
The problem is that in my code, I have two calls to the function one right after the other, and they're overlapping because it appears that setInterval()
doesn't stop the normal flow of code. It's hard to explain, here's my jsFiddle that shows what happens.
Here's my function:
function type(text) {
if(intv == null) {
var textArray = text.split("");
var length = textArray.length - 1;
var i = 0;
$("#div").append('<p class="text' + n + '"></p>' + "\n"); // creates a paragraph to print every character to (n is a global var)
intv = setInterval(function() {
if(i <= length) {
var a = textArray[i];
$('#div .text' + n).text($('#div .text' + n).text() + a); // appends the character to the paragraph created above
i++;
}
else {
alert("stopping interval..."); // just lets me know when the interval is stopping
clearInterval(intv);
intv = null;
n++;
}
}, 60);
}
else {
alert("the interval is not null"); // lets me know if the previous setInterval has stopped firing
}
}
And here's how I'm calling it:
type("string one");
type("string two");
The problem is that both "string one" and "string two" end up firing at the same time, and either start infinitely looping or only the first one works.
I really don't know if there's a way to fix this, and my searches have yielded nothing. I don't know if this is a common problem or not. Is there a way that I can keep the second string from firing until the first one is done?
Thanks ahead of time.
Upvotes: 0
Views: 3656
Reputation: 13967
Have a look here: http://jsfiddle.net/ymxu5/12/
First, set up a function queue that will keep track of where we are:
function Queue(){
this._fns = [];
this._args = [];
this._running = false;
var that = this;
this.add = function(fn){
console.log(fn.name + " added");
that._fns.push(fn);
var args = Array.prototype.slice.call(arguments, 1);
that._args.push(args);
if (!that._running)
that.next();
return that;
},
this.next = function(){
if (that._fns.length == 0){
that._running = false;
}
else{
that._running = true;
that._fns.shift().apply(that, that._args.shift());
}
}
};
You'll then need to adapt your methods to use queue.next();
. I've set up the Queue
object to call each function, changing the context of this
to the Queue
object, which is why var queue = this;
is used.
function type(text){
var queue = this;
var $con = $("<p>").appendTo("#div");
var typeText = function(con, txt, cursor){
if (cursor < txt.length){
con.text(con.text() + txt[cursor]);
setTimeout(function(){
typeText(con, txt, cursor + 1);
}, 60);
}
else{
setTimeout(function(){
queue.next();
}, 250);
}
};
typeText($con, text.split(""), 0);
}
function flicker(sel){
var queue = this;
// note that this will cause issues if "sel" selects multiple things
// because it will create multiple callbacks with jQuery, and `next` will
// be called once for each item in "sel"
$(sel).fadeOut("fast", function(){
$(this).fadeIn("fast", function(){
queue.next();
});
});
}
You'll want to have a Queue
object accessible throughout your scope. I made it global here:
var q = new Queue();
var senNum = 0;
$(function(){
$("#button").click(function(){
q.add(type, "Sentence " + (++senNum)).add(flicker, "p:nth-child("+senNum+")");
});
});
I also took the liberty of making the add
method chainable, so you can use it like above.
here's an example of using a few methods to create a console-like interface:
Upvotes: 2
Reputation: 154868
setInterval
really only schedules an interval to be run every so many milliseconds. After that, everything still works, so you can use other modules on the page (it would be pretty useless if you wouldn't be able to).
If you do want to wait until interval #1 has completed, you can call a function that you pass. Something like:
function type(text, functionIfDone) {
// ...
clearInterval(intv);
functionIfDone(); // call the function now because the interval is over
Use it like:
type("string one", function() {
type("string two");
});
If you would like not to use callbacks, then you could use some helper functions that take care of queueing: http://jsfiddle.net/pZtje/.
var currentInterval;
var intervals = [];
var startInterval = function(func, time) {
intervals.push({
func: func,
time: time
});
checkQueue();
};
var checkQueue = function() {
if(currentInterval == null) {
var item = intervals.shift();
if(item) {
currentInterval = setInterval(item.func, item.time);
}
}
};
var stopInterval = function() {
clearInterval(currentInterval);
currentInterval = null;
checkQueue();
};
// Usage:
var i = 0;
function foo() {
console.log(i);
i++;
if(i === 3) {
i = 0;
stopInterval();
}
}
startInterval(foo, 1000);
startInterval(foo, 1000);
Upvotes: 3