Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Joe Gores
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Joe Gores

Using SetTimeout() To Delay The Closing Of A Related UI Element Based On User Interactions

By
Published in Comments (11)

The other day, in the comments of my bottom-positioned slideUp() / slideDown() jQuery post, Hardy asked me if there was a way to delay the closing of a bottom-positioned element until the user had a chance to properly interact with it. This type of interaction model - related UI actions - is common in web application design and is typically powered by Javascript's setTimeout() and clearTimeout() functions. While the demo I have below is for a fixed, bottom-positioned element, this same approach can be applied to many related UI interaction scenarios.

In Javascript, there are two functions that allow a programmer to execute something "in the future." These are setTimeout() and setInterval(). Both of these functions take two arguments: a function reference and an interval in milliseconds:

  • setTimeout( function, interval )
  • setInterval( function, interval )

NOTE: There are actually other ways to invoke these functions; but, using a function reference is the only way that I would recommend using them.

Each of these functions will invoke the given callback (function argument) after the given interval has passed. The difference between these two functions is that setTimeout() will only invoke the callback once whereas setInterval() will invoke the given callback over and over again, pausing between invokations for a duration defined by the given interval.

When you call these functions - setTimeout() and setInterval() - they pass back a unique token that represents the timer that you have just configured:

var timerID = setTimeout( function, interval );
var timerID = setInterval( function, interval );

There is no requirement to store this token; however, storing this token will allow you to cancel the future invocation of your callbacks, should the conditions of your application change. Once you have these timer tokens, you can cancel the timers using either the clearTimeout() or clearInterval() function:

  • clearTimeout( timerID )
  • clearInterval( timerID )

As you might have guessed, clearTimeout() can be used in conjunction with the setTimeout() tokens and clearInterval() can be used in conjunction with the setInterval() tokens. Once a timer has been cleared, it will not fire again. Should you want to re-configure a timer, you will need to call either the setTimeout() or setInterval() functions again.

Now that we see how to plan the future execution of callbacks, let's see how these functions can be used to delay UI reactions based on user interactions. In the following demo, we have an external link that triggers the opening of a footer area. If the user does not interact with the footer in a given amount of time (4 seconds), the footer will close automatically.

<!DOCTYPE html>
<html>
<head>
	<title>Delayed Hide Of A Related Element</title>

	<style type="text/css">

		#footer {
			background-color: #F0F0F0 ;
			border-top: 1px solid #CCCCCC ;
			bottom: 0px ;
			display: none ;
			left: 0px ;
			position: fixed ;
			width: 100% ;
			}

		#footerInner {
			padding: 10px 15px 35px 15px ;
			}

	</style>
</head>
<body>

	<h1>
		Delayed Hide Of A Related Element
	</h1>

	<p>
		<a id="openFooter" href="##">Show Footer</a>
	</p>


	<div id="footer">
		<div id="footerInner">

			<p>
				Hello - I am the footer. I might have a lot of
				neat things in me to which the the user wants
				to gain access.
			</p>

		</div>
	</div>


	<!-- Load scripts after our DOM is ready. -->
	<script type="text/javascript" src="./jquery-1.4.2.js"></script>
	<script type="text/javascript">

		// Get references to our key dom elements.
		var openFooterLink = $( "#openFooter" );
		var footer = $( "#footer" );


		// Create a function that sets an inactive timer on the
		// footer. This will be called when the footer is opened
		// or when the user mouses out of the footer.
		function setInactiveTimer(){
			// We are goint to store this timer as a property of
			// the footer itself.
			footer.data(
				"inactiveTimer",
				setTimeout( closeFooter, (4 * 1000) )
			);
		}


		// I remove any inactive timer on the footer.
		function clearIntactiveTimer(){
			clearTimeout( footer.data( "inactiveTimer" ) );
		}


		// I open the footer.
		function openFooter(){
			// Open the footer.
			footer.slideDown( 500 );
		}


		// I close the footer.
		function closeFooter(){
			// Slide the footer up (to close it).
			footer.slideUp( 500 );
		}


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Hook up the open footer link. When we do this, not only
		// do we want to open the footer, we also want to set a timer
		// so that the footer will close if it is not being used.
		openFooterLink.click(
			function( event ){
				// Cancel the default event as this is not a true
				// link action.
				event.preventDefault();

				// Check to see if the footer is currently animating.
				// If it is, then we don't want to listen to this
				// click as it might be a double-click.
				if (footer.is( ":animated" )){
					return;
				}

				// Clearn any existing timer since the user clearly
				// want to interact with the footer.
				clearIntactiveTimer();

				// Call the open footer.
				openFooter();

				// Set the inactive timer.
				setInactiveTimer();
			}
		);


		// Attach a mouse enter handler on the footer to hook into
		// user interaction. If the user mouses over the footer,
		// then we want to remove any timers.
		footer.mouseenter(
			function(){
				// Check to see if the footer is currently
				// animating. If it is, then let's ignore since
				// we are not wired to stop closing mid-animation.
				if (footer.is( ":animated" )){
					return;
				}

				// First, clear any existing timer that might take
				// control of the footer display.
				clearIntactiveTimer();
			}
		);


		// Attach a mouse exit handler on teh footer to hook into
		// user disinterest. If the user mouses out of the footer,
		// then we want to start thinking about closing it.
		footer.mouseleave(
			function(){
				// Check to see if the footer is currently
				// animating. If it is, then let's ignore since
				// we are not wired to stop closing mid-animation.
				if (footer.is( ":animated" )){
					return;
				}

				// Set inactivity timer for future-close.
				setInactiveTimer();
			}
		);

	</script>
</body>
</html>

In this demo, the user can actively engage in three different interactions:

  • Open the footer.
  • Mouse into the footer.
  • Mouse out of the footer.

When the user clicks the open-footer link, we want to show the footer and configure a timer that will close the footer if the user chooses not to engage with it. We do this through the use of setTimeout(). In the above code, if the user does not engage with the footer within 4,000 milliseconds (4 seconds), our timer will execute the closeFooter() function, sliding the footer down and out of view.

If the user does choose to engage with the footer, however, we want to make sure that the footer does not close while the user is interacting with it. As such, when the user mouses into (mouseenter) the footer, we use clearTimeout() to cancel the future invocation of our closeFooter() callback.

In this particular demo, I am checking the animation status of the footer before I perform any further UI logic. In this approach, we don't have to worry about double-clicking and mid-animation interactions. We can always expand our logic to deal with such situations later; however, that quickly adds complexity that is not relevant for this explanation.

While approaches to this kind of situation may vary widely, I think you'll find that they typically operate off of the setTimeout() and clearTimeout() functions. I hope that this helps shed some light on delayed interface reactions based on user interactions.

Want to use code from this post? Check out the license.

Reader Comments

198 Comments

FYI - I've been meaning to mention this for a while, but you should consider increasing the volume on your mic. I always end up having to jack up my volume to hear any of your screencasts. The volume on your screencasts is easily 70%+ quieter than other sources I listen to. I have Winamp at 20% volume and it's still way louder than your screencasts. In order to hear you, I always have to turn off any other audio and jack the volume way up.

6 Comments

<stron>Wow Ben - I did not expect such a amount of work just to make happen what I had in mind but it is exactly what I was looking for. Very well done as always. Thank you very, very much for spending your time on that. I think I can use that very often.

15,902 Comments

@Cowboy,

Very cool plugins - you're like a jQuery plugin wizard. I remember at last year's jQuery conference you told me you had a lot of ideas for plugins... no doubt! I particularly the like throttle() concept.

@Dan,

I agree with you on that 100%. But, I'll be straight-up honest - I have no idea how to do that. Would that be a system setting, or an app setting (JING) do you think?

Also, I seem to remember you also writing some "debouncing" code a good while ago.

@Hardy,

Happy to help. Take a look at Ben Alman's jQuery plugins listed earlier in the comments; now that you are more familiar with the concept, his plugins should simplify your life a bit.

6 Comments

@Ben
Yes, I have seen that plugin but you know what. All those plugins make you forget (or never tell you) how it all actually works. So I like the plugins in general since they speed up our work but I also like a good Tutorial very much. That is what makes me smart at the end. All other is just convenient. Looking forward to your next Tuto. Have a good one...

15,902 Comments

@Hardy,

I completely agree - sometimes it's great to pick things apart or go back to the basics to remember how they work in the first place.

14 Comments

I have used setTimeout() many a time, and never knew it returned a token or that you could use that to clear it! Awesome! All this time, I would have a global variable that I would set that would flag the scheduled function whether it should proceed or abort (wrap it's actions on a big if block).

This is much cleaner.

15,902 Comments

@Joshua,

I used to go the "flag" route as well; having a way to programmatically clear a timer will simplify your life greatly.

6 Comments

Ben....today I feel very "blocked" and maybe you can give me a hint to make it work. I use your script but I have 2 openFooterLink trigger on my site for opening the footer panel. 1 should work on mouseenter the other one on click. I just can not make it work. Can you point me? Cheers to NYC from rainy Berlin.

6 Comments

@Ben - I figured out on my own. It was some kind of strange. I was on the right track with creating another var for the second trigger and setting up a click function for it but it was not working because the trigger was wrapped in a <a> tag (as I found out later). It always worked with mouseenter but not with the click event. I then wrapped my trigger in a <span> tag and it all worked like a charme.
Mission completed.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel