Ad

Write/delete Text By Character

- 1 answer

I try to make change of any text by char by char (show text by char, delete text by char and show another one char by char).

What I actually have?

var i = 0;
var terms = ['text  <b>bold</b>', 'longer text <b>bold</b>', '<b>bold</b> text 3'];

var timer = setInterval(function() {
    var el = $('#el');
    var wr = $('#wr');

    setInterval(function() {
        var str = el.html(); // doesn't work (still shows all content, not sliced one)
        el.html(str.substring(0, str.length - 1));
    }, 300 / str.length); // (300 / str.length) - do all animation in 300s

    i++;

    if (i === 3) {
        i = 0;
    }

}, 2500);

I have problem with slicing last char, so I don't get to adding new text so far :-(

One of variants I tried:

...
var text = terms[i].split('');

setInterval(function() {
        el.html(text); // add sliced text in loop... not working as expected            
        // ...    
        text = text.slice(0, -1); // slice text by last character
    }, 300 / text.length); 

Okay, due to the comments a little bit explanation

I have an element

<span id=el>text <b>bold</b></span>

In 300ms interval I need to remove this text char by char.

<span id=el>text <b>bold</b></span>
<span id=el>text <b>bol</b></span>
<span id=el>text <b>bo</b></span>
<span id=el>text <b>b</b></span>
<span id=el>text <b></b></span> // remove 'b'
<span id=el>text</span> // remove ' ' and empty bold
<span id=el>tex</span>
<span id=el>te</span>
<span id=el>t</span>
<span id=el></span>
// now element is empty, since start it's 300ms

// and now I need to put there new text, char by char (whole phrase 300ms again)
<span id=el>l</span>
<span id=el>lo</span>
<span id=el>lon</span>
...
<span id=el>longer tex</span>
<span id=el>longer text</span>
<span id=el>longer text </span> // add space
<span id=el>longer text <b>b</b></span> // add 'b' into bold
<span id=el>longer text <b>bo</b></span>
<span id=el>longer text <b>bol</b></span>
<span id=el>longer text <b>bold</b></span>
// after 2500ms remove this char by char again and replace by third. Etc.

Etc. Can tou help me with that please? Tried that for last 2 days, many attempts, no result...
Thanks

Ad

Answer

This is how I would organize my code to shrink and grow an element. The only sensible way I can do this is to first replace < and > by the corresponding entity codes &lt; and &gt; so that these characters are not interpreted as actual tags. These 4-letter entity codes will be removed and added as a single unit. In this way you can shrink the string one quasi-character at a time from right to left and still have valid HTML at all times.

The Promise api (well, acually jQuery's $.Deferred version of this) is used to be able to know in a deterministic fashion when the shrink-grow cycle, which is an asynchronous process, has completed to then start the 2500 ms delay (which is another asynchronous process) before beginning anew.

$(function() {

    function shrink_grow(resolve, term)
    {
        term = term.replace(/</g, '&lt;').replace(/>/g, '&gt;');

        let el = $('#el');
        el.html(term);

        let interval = setInterval(shrinker, 30);

        function shrinker()
        {
            let str = el.html();
            let n = str.length >= 4 && (str.endsWith('&gt;') || str.endsWith(`&lt;`)) ? 4 : 1;
            el.html(str.substr(0, str.length - n));
            if (str.length === 0) {
                clearInterval(interval);
                interval = setInterval(grower, 30);
            }
        }


        function grower()
        {
            let str = el.html();
            if (str.length == term.length) {
                clearInterval(interval);
                resolve(undefined); // we are done
            }
            else if (str.length <= term.length - 4 && (term.substr(str.length + 1, 4) == '&lt;' || term.substr(str.length + 1, 4) == '&gt;')) {
                el.html(term.substr(0, str.length + 4));
            }
            else {
                el.html(term.substr(0, str.length + 1));
            }
        }

    }

    function pause(milliseconds)
    {
        // Create a new Deferred object
        var deferred = $.Deferred();

        // Resolve the Deferred after the amount of time specified by milliseconds
        setTimeout(deferred.resolve, milliseconds);

        return deferred.promise();
    }


    let terms = ['text <b>bold</b>', 'longer text <i>italic</i> text', '<b>bold</b> text 3'];
    let term_number = 0;
    let deferred = $.Deferred();
    let promise = deferred.promise();
    shrink_grow(deferred.resolve, terms[term_number++]);
    promise.then(function() {
        pause(2500).then(function() {
            let deferred = $.Deferred();
            let promise = deferred.promise();
            shrink_grow(deferred.resolve, terms[term_number++]);
            promise.then(function() {
                pause(2500).then(function() {
                    let deferred = $.Deferred();
                    let promise = deferred.promise();
                    shrink_grow(deferred.resolve, terms[term_number++]);
                    promise.then(function() {
                        console.log('done');
                    });
                });
            });
        });
    });

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="el"></span>

And Keeping the tags intact

This is very complicated, however:

$(function() {

    let TOTAL_TIME = 300;

    function shrink_grow(resolve, term)
    {
        let el = $('#el');

        let matches = term.match(/<([^>])+>(.*?)<\/\1>/); // look for internal tag
        let internalTagTextLength = matches ? matches[2].length : 0;
        let internalTagText = internalTagTextLength ? matches[2] : '';
        let strlen = term.length;
        if (matches) {
            strlen -= matches[1].length * 2 + 5;
        }
        let shrinkGrowInterval = TOTAL_TIME / strlen;
        if (shrinkGrowInterval < 16) {
            shrinkGrowInterval = 16;
        }

        let interval = setInterval(grower, shrinkGrowInterval);

        function shrinker()
        {
            let str = el.html();
            let matches = str.match(/<([^>])+>(.*?)<\/\1>$/); // <i>text</i> at end of string, for example
            if (matches) {
                let str2 = matches[2];
                if (str2.length < 2) { // get rid of entire tag
                    str2 = matches[0];
                    let n = str2.length;
                    let l = str.length - n;
                    el.html(str.substr(0, l));
                    if (l === 0) {
                        clearInterval(interval);
                        resolve(undefined); // we are done
                    }
                }
                else {
                    let str2a = str2.substr(0, str2.length - 1);
                    str = str.replace(/<([^>])+>(.*?)<\/\1>$/, '<' + matches[1] + '>' + str2a + '</' + matches[1] + '>');
                    el.html(str);
                }
            }
            else {
                el.html(str.substr(0, str.length - 1));
                if (str.length === 0) {
                    clearInterval(interval);
                    resolve(undefined); // we are done
                }
            }
        }


        function grower()
        {
            let str = el.html();
            if (str.length == term.length) {
                clearInterval(interval);
                interval = setInterval(shrinker, shrinkGrowInterval);
            }
            else {
                let matches = term.substr(str.length).match(/^<([^>])+>(.*?)<\/\1>/);  // start of <i>text</i>, for example?
                if (matches) {
                    let str2 = '<' + matches[1] + '>' + matches[2].substr(0, 1) + '</' + matches[1] + '>';
                    el.html(str + str2);
                }
                else {
                    let matches = str.match(/<([^>])+>(.*?)<\/\1>$/); // <i>text</i> at end of string, for example
                    if (matches) {
                        let str2 = matches[2];
                        let l = str2.length;
                        if (l == internalTagTextLength) {
                            el.html(term.substr(0, str.length + 1));
                        }
                        else {
                            let str2a = internalTagText.substr(0, l + 1);
                            str = str.replace(/<([^>])+>(.*?)<\/\1>$/, '<' + matches[1] + '>' + str2a + '</' + matches[1] + '>');
                            el.html(str);
                        }
                    }
                    else {
                        el.html(term.substr(0, str.length + 1));
                    }
                }
            }
        }

    }


    let terms = ['text <b>bold</b>', 'longer text <i>italic</i> text', '<b>bold</b> text 3'];
    let nTerms = terms.length;
    let termNumber = -1;

    function callShrinkGrow()
    {
        if (++termNumber >= nTerms) {
            termNumber = 0;
        }
        let deferred = $.Deferred();
        let promise = deferred.promise();
        shrink_grow(deferred.resolve, terms[termNumber]);
        promise.then(callShrinkGrow);
    }

    callShrinkGrow();

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<span id="el"></span>

Ad
source: stackoverflow.com
Ad