Search code examples
javascriptsettimeoutmultiple-instancesscopingcleartimeout

Clearing multiple timeouts properly


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>
and a Fiddle script: https://jsfiddle.net/phjzfw15/

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>
... and the relative fiddle if you prefer it there: https://jsfiddle.net/phjzfw15/1/

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!


Solution

  • 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>