Sticky Captions Concept

A little trick on how to make captions of thumbnails or items “sticky” in order to stay visible in the window or viewport.

Sticky Captions Concept

When creating thumbnail grids, we usually want to show image captions on hover to provide more information about the item. Image captions are usually shown in a very specific part of the thumbnail, either on the top, the middle or the bottom. When adding captions to the bottom of a thumbnail it can happen that a thumbnail that is overflowing the viewport (i.e. is partly beyond the “fold”) is being hovered but the caption won’t be seen because it appears on the bottom part of the image that is not visible. The user would have to scroll the page in order to see the bottom of the item and eventually the caption.

A small trick can solve that problem by simply making the caption “sticky”. This would mean that the caption will be visible not only at the bottom of every thumbnail but also in any place, sticking at the bottom of the page, if the thumbnail hovered is overflowing the current view.

What we basically do is to imitate position: sticky but since it’s not yet supported in many browsers, we’ll do a bit of JavaScript to achieve the same result. We’ll be using jQuery.

The main idea is to see when a hovered element overflows the viewport and show the caption in the right place by changing its position from absolute to fixed.

As an example, let’s take a simple grid with figures and figcaptions:

<div class="grid clearfix" id="grid">

	<figure>
		<img src="images/4.jpg">
		<figcaption>
			<a href="http://drbl.in/fWMT">Fall 7 Times Stand Up 8</a> by Erika Mackley
		</figcaption>
	</figure>

	<figure><!-- ... --></figure>
	<figure><!-- ... --></figure>
	<figure><!-- ... --></figure>
	<!-- ... -->
	
</div>

In our demo we use jQuery Masonry for creating a neat grid.

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

Let’s take a look at the structural styling of the figcaption element (to see all the styles, take a look at the style.css). The caption should be absolute and we will set a left of auto. This is important because when we switch to fixed positioning, we want the caption to start at the left of its parent container. We set a bottom value of -60 pixels in order to hide the caption (minus its own height). Note that the parent container needs to have its overflow set to “hidden”.

We also add a little transition which will animate the bottom to 0 when we hover over the figure:

.grid figure figcaption {
	position: absolute;
	left: auto;
	width: 100%;
	height: 60px;
	bottom: -60px;
	transition: bottom 0.2s ease-in-out;
}

.grid figure:hover figcaption {
	bottom: 0px;
}

Now, we want to check if the thumbnail that is being hovered is cut off at the bottom, i.e. out of the window view. If yes, we will change the caption position to fixed and set a width to it (otherwise it would just happily expand until the end of the page).

Let’s start by creating a custom jQuery selector called :bottomInViewport that we will help us determine if the caption of the image should be rendered or not.

$.extend( $.expr[':'], {
	bottomInViewport : function( el ) {
		var scrollTop = ( document.documentElement.scrollTop || document.body.scrollTop ),
			elOffsetTop = $( el ).offset().top,
			elH = $( el ).height(),
			descrH = $( el ).find( 'figcaption' ).outerHeight(true),
			winH = ( window.innerHeight && window.innerHeight < $( window ).height() ) ? window.innerHeight : $( window ).height();

		return ( elOffsetTop + elH > scrollTop && elOffsetTop + elH < scrollTop + winH ) || ( scrollTop + winH - elOffsetTop < descrH );
	}
});

The function returns true if the element is completely inside the viewport, meaning that the bottom part is not cut off. It also returns true if the element is not in the viewport but the visible part is just too small to fit the caption. In these cases our script shouldn’t do anything and the caption will simply show as usual. If the function returns false, we will show the caption by setting its position to fixed. Since we defined the bottom to be 0 in the CSS, it will show at the bottom of the page, right where we need it. We’ll also have to set the width to its parent’s width (the figure):

function changeToFixed( $description, itemWidth ) {
	$description.css({ position: 'fixed', width: itemWidth });
}

function resetStyle( $description, delay ) {
	setTimeout( function() { $description.css({ position: 'absolute', width: '100%'}); }, delay || 0 );
}

We will need to bind the mouseenter and mouseleave events to the items and also the scroll event to the window while hovering over an item:

$items.on( 'mouseenter mouseleave', function( event ) {

	var $item = $( this ), itemWidth = $item.width(),
		$description = $item.find( 'figcaption' );

	switch( event.type ) {
		case 'mouseenter' :

			if( !$item.is( ':bottomInViewport' ) ) {
				$item.data( 'sticky', true );
				changeToFixed( $description, itemWidth );
			}
			
			$( window ).on( 'scroll', function () {
				var inviewport = $item.is( ':bottomInViewport' );
				if( !inviewport && !$item.data( 'sticky' ) ) {
					$item.data( 'sticky', true );
					changeToFixed( $description, itemWidth );
				}
				else if( inviewport && $item.data( 'sticky' ) ) {
					$item.data( 'sticky', false );
					resetStyle( $description );
				}
			} );

			break;
		
		case 'mouseleave' :

			if( $item.data( 'sticky' ) ) {
				$item.data( 'sticky', false );
				resetStyle( $( this ).find( 'figcaption' ), 200 );
			}
			$( window ).off( 'scroll' );
			break;
	}

} );

When we hover over an item, we check if we should apply our trick or not. Also, we bind the scroll event to the window, where we will check if the trick should be applied or reset as we scroll. When we leave the item, we unbind the scroll event from the window and we reset the item’s style (if the trick was previously applied to it).

And that’s it! Take a look at the demo and hover over an item that is cut off at the bottom to see the effect. And then scroll. You will see how the caption sticks at the bottom until its “natural” position is reached.

I hope you enjoyed this little trick and find it useful!

Credits: the demo features Dribbble shots of illustrations by Erika Mackley.

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

👾 Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 17

Comments are closed.
  1. Really nice trick (as always)! I did an article similar to this in my blog, to achieve a nice caption on :hover but only using CSS. This jQuery aproach is very interesting and I’ll keep it on my bookmarks.
    Keep up the good work!!

  2. This is extremely complex in it’s simplicity. I love it. But… I have a quick question. I’m not too familiar with mobile touch events & I don’t have a smart phone (i know i’m crazy). Do the captions still apear on a touch screen? Just curious. All the best – G

    • Hi George, this example is specifically for dealing with hovering items… for touch devices I’d probably show all captions. It might be interesting to push this idea forward and make all the captions sticky for this case. Thanks for your feedback, cheers, ML

  3. That is very very nice. I spent a minute trying to figure out what this demo actually did. It was so seamless and intuitive It took me a while to realise that those captions wouldn’t be seen on any other design!

  4. A very nice idea. Having a caption appears on hover is very common. But having it stays visible on a view port is a superb idea. Thumbs up.

  5. At first I didn’t understand what the demo was showing me that was new. It’s the subtlety of the sticky behaviour, at the edge of the viewport, that makes this very useful.

  6. Very nice tutorial! May i suggest a little edit?
    When you’re calling $( window ).height() , as for jQuery 1.8+, what you’re actually recieving is document.documentElement.clientHeight . It may change in future versions, so maybe you can write the expression this way:

    winH = ( window.innerHeight && window.innerHeight < document.documentElement.clientHeight ) ? window.innerHeight : document.documentElement.clientHeight

    for future consistency’s sake 😉
    Keep up the good work!

  7. I’ve said it before and I’ll say it again—Mary Lou keep killin em’ with these tutorials!

    The demos are always so fresh! I can’t wait to try this on a new project I’m working on.
    Awesome work as always 🙂

    • Zack, my intention was not to show how to do sliding captions 🙂 The point here is that they are sticky, meaning that if you hover over a thumbnail that expands below the fold, the caption will be visible and slide out from the bottom of the page… This is a very subtle effect and your example is showing a classic slide out caption. Hope this clarifies the goal of this tut, cheers, ML

  8. Hello, I would like knowing how to enlarge the images with overlay like lightbox. Thanx