Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 10 of 10
  1. #1
    New Coder
    Join Date
    Aug 2017
    Posts
    23
    Thanks
    8
    Thanked 0 Times in 0 Posts

    Script won't run when I'm off the page

    I have a script on a webpage that adds a typewriter effect to a sentence. The problem is that when your aren't actively looking at the page it doesn't write. This is a problem as i have a very long text that needs to run in a certain time, so leaving the page messes things up a bit.

    heres the basic script for the type effect
    Code:
    <!DOCTYPE html>
    <html>
    <body>
    
    <h1>Story or somthing</h1>
    
    <button onclick="typeWriter()">Click me</button>
    
    <p id="demo"></p>
    
    <script>
    var i = 0;
    var txt = 'Lorem ipsum dummy text blabla.';
    var speed = 50;
    
    function typeWriter() {
      if (i < txt.length) {
        document.getElementById("demo").innerHTML += txt.charAt(i);
        i++;
        setTimeout(typeWriter, speed);
      }
    }
    </script>
    
    </body>
    </html>
    If someone could tell me a way to get the script to continue writing even when I'm not on the page, that'd be much appreciated.

  2. #2
    Senior Coder xelawho's Avatar
    Join Date
    Nov 2010
    Location
    Here
    Posts
    3,944
    Thanks
    58
    Thanked 702 Times in 697 Posts
    You mean the page is open (like in another tab) or it's not loaded in the browser at all?

  3. #3
    New Coder
    Join Date
    Aug 2017
    Posts
    23
    Thanks
    8
    Thanked 0 Times in 0 Posts
    its open but in a different tab

  4. #4
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,424
    Thanks
    4
    Thanked 486 Times in 474 Posts
    Yes, when a tab is not focused scripting execution is delayed if not halted when/if the script hogs too many resources. This is not an uncommon situation.

    Generally these types of "slow output trickery" are accessibility junk, only serves to piss off users, and should be avoided, BUT if you insist on doing it, STOP screwing around with innerHTML to do this.

    innerHTML has security risks since it can write code, is PAINFULLY slow as it forces the DOM to be parsed back to HTML, the insertion made, and the new HTML parsed into a new DOM for a full re-parse and re-render. This is why going straight to the DOM is the preferred approach.

    Even so, every blasted character is going to get it's own DOM node and that's gonna be painful in terms of overhead. A reason this type of scripted trickery hasn't been commonplace on websites since the '90's.

    Code:
    <!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
    	<title>TT script demo</title>
    </head><body>
    
    <h1>Story or somthing</h1>
    
    <!--
    	the button you have here has no business in the HTML since 
    	it's scripting off only functionality!
    -->
    
    <noscript>
    	<p class="scriptWarning">
    		This page's full functionality requires JavaScript
    	</p>
    </noscript>
    
    <!--
    	if this is teletype-style text, use the teletype TAG!
    	Yes, there's a tag for this.
    -->
    <tt id="demo"></tt>
    
    <script>
    (function(d) { // SIF/IIFE isolates scope
    
    	var
    		i = 0,
    		txt = 'Lorem ipsum dummy text blabla.',
    		speed = 50,
    	// get this only once, not every blasted time!
    		demo = d.getElementById('demo'),
    	/*
    		we make the button in the scripting so that 
    		
    			1) the function can be isolated from global scope
    			
    			2) elements that serve no purpose scripting off aren't present
    				when scripting is unavailable.
    	*/
    		button = d.createElement('button');
    	
    	button.type = 'button'; // remember, default is submit
    	/*
    		normally we wouldn't use Element.onclick, but since the Element isn't
    		even on the DOM yet, it's fine.
    	*/
    	button.onclick = typeWriter;
    	button.appendChild(d.createTextNode('Click Me'));
    	demo.parentNode.insertBefore(button, demo);
    		
    	function typeWriter() {
    		if (i < txt.length) {
    			demo.appendChild(d.createTextNode(txt.charAt(i++));
    			setTimeout(typeWriter, speed);
    		}
    	}
    
    })(document);
    
    </script>
    
    </body></html>
    That said, even with that the timeout may be halted depending on the browser, it's just the nature of the beast. I would consider trying to 'onblur' hook the window to populate the full text, but that too is unreliable.

    But again, I wouldn't do this "effect" by choice, and if a client insisted on it, I'd probably put the text into the TT tag, then copy the text out of it on the fly in a script loaded right before </body> into the text to be displayed variable. As a load-blocking script (boo, hiss) it would hang the render so the text likely wouldn't show in the tag until after CSS loads, by which time the scripting would have removed it.

    Code:
    <!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
    	<title>TT script demo</title>
    </head><body>
    
    <h1>Story or somthing</h1>
    
    <!--
    	the button you have here has no business in the HTML since 
    	it's scripting off only functionality!
    -->
    
    <!--
    	if this is teletype-style text, use the teletype TAG!
    	Yes, there's a tag for this.
    -->
    <tt id="demo">Lorem ipsum dummy text blabla.</tt>
    
    <script>
    (function(d) { // SIF/IIFE isolates scope
    
    	var
    		i = 0,
    		txt = '',
    		speed = 50,
    	// get this only once, not every blasted time!
    		demo = d.getElementById('demo'),
    		txt = demo.textContent,
    	/*
    		we make the button in the scripting so that 
    		
    			1) the function can be isolated from global scope
    			
    			2) elements that serve no purpose scripting off aren't present
    				when scripting is unavailable.
    	*/
    		button = d.createElement('button');
    		
    	/* node flush the demo element's content */
    	while (demo.firstChild) demo.removeChild(demo.firstChild);
    	
    	/*
    		normally we wouldn't use Element.onclick, but since the Element isn't
    		even on the DOM yet, it's fine.
    	*/
    	button.type = 'button'; // remember, default is submit
    	button.onclick = typeWriter;
    	button.appendChild(d.createTextNode('Click Me'));
    	demo.parentNode.insertBefore(button, demo);
    		
    	function typeWriter() {
    		if (i < txt.length) {
    			demo.appendChild(d.createTextNode(txt.charAt(i++));
    			setTimeout(typeWriter, speed);
    		}
    	}
    
    })(document);
    
    </script>
    
    </body></html>
    This way "scripting off / blocked / disabled / told to sod off" the content will still display as normal, AND you can put the content where it belongs, in the markup. End result should (in theory) be more search friendly.
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  5. #5
    Senior Coder
    Join Date
    Aug 2010
    Posts
    1,278
    Thanks
    46
    Thanked 278 Times in 276 Posts
    Here I present a method of adding the text in a fashion that is regulated by real time
    and is less subject to the vagaries of setTimeout.
    As always DS has presented a concise and relevant tutorial which may be applied to the following code …
    Code:
    <!DOCTYPE html>
    <html>
    <body>
    <h1>Story or somthing</h1>
    <button onclick="typeWriter()">Click me</button>
    <p id="demo"></p>
    <script>
    function typeWriter() {
    var i = 0;
    var txt = 'Lorem ipsum dummy text blabla.';
    var speed = 50;
    var start = new Date().getTime();
     (function write(){ 
        if (i < txt.length ) {
            while(new Date().getTime() > start + ( i * speed ) ) {
               demo.appendChild(document.createTextNode(txt.charAt(i++)));
            }
            setTimeout( write, 10 );
        }
     })();
    }
    </script>
    </body>
    </html>
    Last edited by DaveyErwin; May 16th, 2019 at 06:42 PM.

  6. #6
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,424
    Thanks
    4
    Thanked 486 Times in 474 Posts
    Quote Originally Posted by DaveyErwin View Post
    while(new Date().getTime() > start + ( i * speed ) ) {
    I'm curious why you would do that, since between the timeout period shorter than the minimum supported on most browsers, and then hogging the execution tree with the loop, the tab would become ill behaved under Blink/Webkit, and might trigger FF's "This script is taking too long to respond" safeties. Much less running it flat out like that would be murder on mobile battery life.

    ... and really it's still using timeout so... what's the difference? I mean realistically timer jitter / granularity alone would cancel out any advantages of that approach, and the disadvantages are many.

    NOT picking on it, I'm just wondering the reasoning for it.

    Though if worried about how it's not 100% accurate, setInterval and then unsetting the interval might be an even better approach, since adding one character if you use the DOM instead of innerDerpTML should have little to no overhead and certainly not take the interval period.

    Really though if we can tell older browsers to sod off -- or live with a polyfill -- window.requestAnimationFrame might be an even better answer than interval/timeout; assuming it isn't needed for anything else. The only issue being it would be called way more often, again introducing that battery life penalty. That and by definition requestAnimationFrame's callbacks are halted when the tab isn't focused.

    I think in most browsers the only scripting not halted are modern push requests, pending AJAX, and object/embed. (flash)
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  7. #7
    Senior Coder
    Join Date
    Aug 2010
    Posts
    1,278
    Thanks
    46
    Thanked 278 Times in 276 Posts
    Quote Originally Posted by Fin View Post
    I have a script on a webpage that adds a typewriter effect to a sentence.
    The problem is that when your aren't actively looking at the page it doesn't write.
    This is a problem as i have a very long text that needs to run in a certain time, so leaving the page messes things up a bit.
    If someone could tell me a way to get the script to continue writing even when I'm not on the page, that'd be much appreciated.

    While this script may also fail to write while the client is in another tab,
    it has the advantage of the while loop.
    Once the writing is resumed the while loop will run until the number
    of chars written is appropriate for the actual elapsed time.
    If the chars are not exhausted then the setTimeout will be triggered
    and the "typewriter" effect will continue.
    The result will be to the viewer as if the writing continued while the
    client was in another tab.
    Last edited by DaveyErwin; May 17th, 2019 at 01:35 AM.

  8. #8
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,424
    Thanks
    4
    Thanked 486 Times in 474 Posts
    Was thinking about it overnight -- well, my night -- and I really think the window blur is what to trap. When it loses focus just belt out / populate the whole thing.

    There were typo's in my original posts code (since I was up with insomnia), so here's a working version.

    Code:
    <!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
    	<title>TT script demo</title>
    </head><body>
    
    <h1>Story or somthing</h1>
    
    <!--
    	the button you have here has no business in the HTML since 
    	it's scripting off only functionality!
    -->
    
    <tt id="demo">
    	Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vel risus ac ipsum mollis lacinia in in diam. Ut elementum id lacus vel pulvinar. Suspendisse tincidunt dui ac dignissim bibendum. Phasellus ullamcorper semper euismod. Cras vel posuere orci, viverra interdum sapien. Sed porta quam et ex egestas, vitae sodales nibh fringilla. Nam venenatis sem lectus, vitae finibus enim aliquam ut. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas et tincidunt dui. Aliquam gravida libero non tempor dictum. Etiam mattis sed leo vitae egestas. Donec ultrices aliquet odio ac facilisis. Nullam pretium odio et porta egestas. Mauris eget justo imperdiet, tempus dolor non, semper erat. Vestibulum vulputate, nunc id consectetur dapibus, enim mi ullamcorper nisl, eget euismod velit augue finibus purus. In hac habitasse platea dictumst.
    </tt>
    
    <script>
    
    (function(d) { // SIF/IIFE isolates scope
    
    	/*
    		A DOM node flush, deleting the child tree elements manually is
    		faster/cleaner than innerHTML = "" in the majority of cases, and
    		doesn't trip any possibly unwanted side-effects.
    		
    		Again, avoid innerHTML when possible.
    	*/
    	function nodeFlush(e) {
    		while (e.firstChild) e.removeChild(e.firstChild);
    	}
    
    	nodeFlush(demo);
    	
    	var
    		i = 0,
    		txt = '',
    		speed = 50,
    	// get this only once, not every blasted time!
    		demo = d.getElementById('demo'),
    		txt = demo.textContent,
    	/*
    		we make the button in the scripting so that 
    		
    			1) the function can be isolated from global scope
    			
    			2) elements that serve no purpose scripting off aren't present
    				when scripting is unavailable.
    	*/
    		button = d.createElement('button');
    		
    	button.type = 'button'; // remember, default is submit
    	button.appendChild(d.createTextNode('Click Me'));
    	demo.parentNode.insertBefore(button, demo);
    	
    	/*
    		normally we wouldn't use Element.onclick, but since the Element isn't
    		even on the DOM yet, it's fine.
    	*/
    	button.onclick = function(e) {
    		e.target.parentNode.removeChild(e.target); // remove the button
    		typeWriter();
    	}
    		
    	function typeWriter() {
    		if (i < txt.length) {
    			demo.appendChild(d.createTextNode(txt.charAt(i++)));
    			setTimeout(typeWriter, speed);
    		} else typeFinish();
    	}
    	
    	/*
    		when it's done I do a node flush and repopulate so that the
    		entire text ends up a single textNode instead of the multiple
    		textnodes the single character appends created. For a little
    		bit of extra execution time this lowers the memory footprint
    		and shrinks the DOM tree. 
    		
    		this routine can also be used when the window blurs to populate it.
    	*/
    	function typeFinish() {
    		nodeFlush(demo);
    		demo.appendChild(d.createTextNode(txt));
    	}
    	
    	/*
    		Nabbing the window blur also gets when the tab loses focus -- such
    		as when switching to another tab. Finish it out / clean it up since
    		interval/timeout/animationFrame events are all suspended when the
    		window doesn't have focus.
    	*/
    	window.addEventListener('blur', function() {
    		if (i < txt.length) {
    			i = txt.length;
    			typeFinish();
    		}
    	});
    
    })(document);
    
    </script>
    
    </body></html>
    I commented the bugger out of it to explain the how/what/why/where of my reasoning.

    Trapping the blur to "finish it off" is IMHO the only 'real' fix for the timeout/interval being suspended whilst the window or tab lacks focus. You navigate away, just fill it.

    I also removed the click button when it's clicked, so that you can't click multiple times to start overlapping timeouts.

    For further expansion I'd consider adding a value in localstorage to say if a user has seen the animation or not yet, and if so skip the animated stuff altogether. As I said before it's the type of thing that could annoy some users, but it would just plain piss them off royally to deal with it every blasted time they visit the page.
    Last edited by deathshadow; May 17th, 2019 at 01:39 AM.
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  9. #9
    Senior Coder deathshadow's Avatar
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    3,424
    Thanks
    4
    Thanked 486 Times in 474 Posts
    Quote Originally Posted by DaveyErwin View Post
    While this script may also fail to write while the client is in another tab,
    it has the advantage of the while loop.
    Once the writing is resumed the while loop will run until the number
    of chars written is appropriate for the actual elapsed time.
    Oh, I get it now. Time buffering, when the window resumes execution it belts out the rest up to however much time the window was out of focus. Makes sense.

    If I were to do that, I'd probably figure out how many timeouts were skipped, and then substr in rather than hard looping, but yeah, that's not bad actually. Timeouts, Intervals, and of course requestAnimationFrame are all halted, but you can't stop the system clock from the browser.
    “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” – C.A.R. Hoare, The 1980 ACM Turing Award Lecture
    http://www.cutcodedown.com

  10. #10
    Senior Coder
    Join Date
    Aug 2010
    Posts
    1,278
    Thanks
    46
    Thanked 278 Times in 276 Posts
    Quote Originally Posted by deathshadow View Post
    Oh, I get it now. Time buffering, when the window resumes execution it belts out the rest up to however much time the window was out of focus. Makes sense.

    If I were to do that, I'd probably figure out how many timeouts were skipped, and then substr in rather than hard looping, but yeah, that's not bad actually. Timeouts, Intervals, and of course requestAnimationFrame are all halted, but you can't stop the system clock from the browser.
    Yes, exactly. My code is less than obvious but you obviously have good comprehension.

    also, I agree that my setTimeout is unnecessarily short so ...
    //setTimeout( write, 10 );
    setTimeout( write, speed );

    Last edited by DaveyErwin; May 18th, 2019 at 04:47 PM.


 

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •