I've been struggling for the past day trying to solve this one.
I'm trying to get some funky text effects going, basically a glorified string creation. It writes a line a bit like a billboard and to do it I've used setTimeout. Thing is I would like to put it in a function so I can reuse it and call it multiple times on different elements.
The problem is that I then need to update the text maybe halfway to a new text. To do so I clear the timeout, but unless the timer variable is outside the scope it doesn't clear.
I can't really have it outside the function because of practicality; I'm not sure how many times it is going to be called and it just feels wrong to declare 20 time variables outside the function.
Here's the code working CORRECTLY on one item (click multiple times to interrupt and restart)
var t;
function writeStats(str,dest) {
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
function randomChar(type){
var pool = "";
if (type == "lowerLetter"){
pool = "abcdefghijklmnopqrstuvwxyz0123456789";
}
else if (type == "upperLetter"){
pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
else if (type == "symbol"){
pool = ",.?/\\(^)![]{}*&^%$#'\"";
}
var arr = pool.split('');
return arr[Math.floor(Math.random()*arr.length)];
}
str = str.split('');
var types = [],
letters = [];
for(var i=0;i<str.length;i++){
var ch = str[i];
if(ch == " "){
types[i] = "space";
continue;
}
else if(/[a-z]/.test(ch)){
types[i] = "lowerLetter";
}
else if(/[A-Z]/.test(ch)){
types[i] = "upperLetter";
}
else {
types[i] = "symbol";
}
letters.push(i);
}
clearTimeout(t);
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i,
len = letters.length,
strCopy = str.slice(0); // Fresh copy of the string
if(start>len){
return;
}
// All the work gets done here
for(i=Math.max(start,0); i < len; i++){
// The start argument and options.step limit
// the characters we will be working on at once
if( i < start+options.step){
// Generate a random character at this position
strCopy[letters[i]] = randomChar(types[letters[i]]);
}
else {
strCopy[letters[i]] = "";
}
}
//el.text(strCopy.join(""));
el = strCopy.join("");
//console.log(el);
$('.'+dest).text(el);
t = setTimeout(function(){
shuffle(start+1);
},500/options.fps);
})(-options.step);
}
$(document).ready(function(){
$(document).click(function(){
writeStats('this sentence is a great one','t1');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>
If I bring the t variable inside the function like so it doesn't work as before:
function writeStats(str,dest) {
var t;
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
function randomChar(type){
var pool = "";
if (type == "lowerLetter"){
pool = "abcdefghijklmnopqrstuvwxyz0123456789";
}
else if (type == "upperLetter"){
pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
else if (type == "symbol"){
pool = ",.?/\\(^)![]{}*&^%$#'\"";
}
var arr = pool.split('');
return arr[Math.floor(Math.random()*arr.length)];
}
str = str.split('');
var types = [],
letters = [];
for(var i=0;i<str.length;i++){
var ch = str[i];
if(ch == " "){
types[i] = "space";
continue;
}
else if(/[a-z]/.test(ch)){
types[i] = "lowerLetter";
}
else if(/[A-Z]/.test(ch)){
types[i] = "upperLetter";
}
else {
types[i] = "symbol";
}
letters.push(i);
}
clearTimeout(t);
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i,
len = letters.length,
strCopy = str.slice(0); // Fresh copy of the string
if(start>len){
return;
}
// All the work gets done here
for(i=Math.max(start,0); i < len; i++){
// The start argument and options.step limit
// the characters we will be working on at once
if( i < start+options.step){
// Generate a random character at this position
strCopy[letters[i]] = randomChar(types[letters[i]]);
}
else {
strCopy[letters[i]] = "";
}
}
//el.text(strCopy.join(""));
el = strCopy.join("");
//console.log(el);
$('.'+dest).text(el);
t = setTimeout(function(){
shuffle(start+1);
},500/options.fps);
})(-options.step);
}
$(document).ready(function(){
$(document).click(function(){
writeStats('this sentence is a great one','t1');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>
If you run the snippet you'll see it doesn't work properly anymore. Clicking repeatedly will show you that the old sentence is still there and it gets overwritten. How can I get this working clearing the timeout correctly inside the function?
I thought the "t" variable was local to each function and a separate instance of it would have been created?
Thanks!
Finally made it to work. For posterities...
var starr = [
'bloop the boop',
'cammy the shadow',
'i like cauliflower',
'bro, i kick u hard',
'like measels? I dont.',
'eat fish and pie'
];
var writer = function(){
var timer;
this.writeStat = function(str,dest) {
var options = { "step" : 8, "fps" : 25, "text" : "" }
str = str.split('');
clearTimeout(timer);
var ll = '';
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i, len = str.length, el;
if(start>=len){
return;
}
ll = ll + str[start];
$('.'+dest).text(ll);
timer = setTimeout(function(){
shuffle(start+1);
},1500/options.fps);
})(0);
}
}
$(document).ready(function(){
var index = 0;
w = new writer;
y = new writer;
$(document).click(function(){
w.writeStat(starr[index],'t1');
y.writeStat(starr[index],'t2');
if (index == 5) index = 0; else index++;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>