How to build a scrolling list with jQuery

Sunday 7th February

This tutorial explains, step-by-step, how to use CSS and jQuery animations to build a simple ‘auto-scrolling’ vertical list.

Let’s be clear, here: the widget I’m about to put together isn’t exactly a stunning new advance in user interface design. But it does produce a nice final result, and you’ll probably learn a little about CSS and jQuery animations along the way.

Requirements

To make things more interesting, we’ll give ourselves some pretty strict requirements about behaviour and display. Many similar devices I’ve seen on the web fail on at least one of these requirements, and this was part of the driver towards putting this tutorial together.

These requirements should make for an interesting enough challenge, whilst offering a reasonable use case from which to study CSS and jQuery. You can check the final demo to verify these requirements, and ensure your understanding of them is correct.

Setup

First, let’s get to the markup, which should be as straightforward as possible:

<ul id="scroller">
    <li>Item one</li>
    <li>Item two</li>
</ul>

I’ve added some default CSS, for the purposes of this demo, in order to demonstrate how to resolve issues relating to spacing caused by any padding and margins present:

#scroller { list-style: none; padding: 1em;
    border: 1px solid #9DB029; margin: 1em 0; }

#scroller li { border: 1px solid #ddd; width: 8em;
    margin: 0.25em; padding: 0.5em; background-color: #eee; }

And here’s how the basic list is displayed:

Adding the new item

The first step, of course, is to add the new item at the beginning of the list. To do this, we’ll use jQuery’s prepend() function:

$('#scroller').prepend('<li>A new item</li>');

which adds a list item as the first child of the list, ensuring it appears right at the top:

This demonstrates the first problem: our list will expand its height to accommodate the items contained within it. We’ll fix that simply by assigning a fixed height to the list, for the duration of our animation:

$('#scroller').css('height', $('#scroller').height());

Unfortunately, the last item now ‘spills out’ of the list, something we definitely don’t want to be happening. This won’t be a problem for now, because we’re going to move everything up so that the original items occupy their original positions, but we’ll have to revisit the ‘overflow’ problem soon.

For now, let’s move everything up by applying a negative margin to the new item:

The size of this margin is simply the total height that the item was occupying, which includes its content height, padding, and borders. Note that it does not include any vertical margins, since they collapse. Thanks to a built-in jQuery function, we can easily calculate this height, and set the margin:

$('#scroller li:first').css('margin-top',
    0 - $('#scroller li:first').outerHeight());

Now we really do have to deal with the overflow problem. Fortunately, it’s very easy to clip the overflowing content using the overflow CSS property:

$('#scrolling').css('overflow', 'hidden');

Note that this applies to the entire list, so it will handle any later situation in which the final list item might overflow the bottom.

Finally, for this initial stage, we need to slide the new item up a bit, just out of view. For this calculation, it’s important to realise why that part of the box is still showing: the list’s padding. Recall that we moved the new item up such that the original top item was back in its starting place. Therefore, we need to move our new item up by the same amount of top padding on the list.

The other factor to consider is that we don’t want anything else to be affected by this movement, in particular, we don’t want the following list items to move up. Relative positioning is the way to move an element whilst ensuring others respect its original position:

var ulPaddingTop = $('#scroller').css('padding-top');
    ulPaddingTop = ulPaddingTop.substr(0, ulPaddingTop.length - 2);
$('#scroller li:first').css('position', 'relative');
$('#scroller li:first').css('top', 0 - ulPaddingTop);

Note that, slightly frustratingly, the value returned for padding-top includes the trailing “px”, so that needs to be stripped with a call to substr().

It’s as if our new list item isn’t even there …

Moving the new item into place

Now we’ve got the new item in the list, but hidden out of view, it’s time to move it into position. First of all, let’s slide it down back to where it was before the previous step, by setting the top back to 0:

$('#scroller li:first').animate({top: 0});

You should see the effect of that step when you click the button above. Next, we’ll undo the other initial positioning that was applied to bring the new item fully into view:

var oldMarginTop = $('#scroller li:first').css('margin-top');
...
$('#scroller li:first').css('margin-top',
    0 - $('#scroller li:first').outerHeight());
...
$('#scrollerli:first').animate({marginTop: oldMarginTop});

Note that the first step — storing the original value of the item’s top margin — needs to take place just after we added it since it needs to be maintained when the item is moved back into its original position:

Again, clicking the button should demonstrate this step’s effect. All that remains is to get rid of the last list item.

Removing the old item and cleaning up

In much the same way that we originally moved the new item up past the list’s top padding, we now need to push the last item below the list’s bottom padding. Once that’s been done, the unwanted item needs to be properly removed from the DOM:

var ulPaddingBottom = $('#scroller').css('padding-bottom');
    ulPaddingBottom = ulPaddingBottom.substr(0, ulPaddingBottom.length - 2);
$('#scroller li:last').animate({top: ulPaddingBottom});
$('#scroller li:last').remove();

And, finally, we should undo any changes we’ve applied to the list to ensure it’s back in its original state:

$('#scroller').css('height', 'auto');
$('#scroller').css('overflow', 'visible');

Apart from being nice and tidy, this helps to minimise any disruption caused by possible changes in text size, caused by the user zooming text, for example.

Putting everything together

Before we finish, let’s consider how to run the animation parts of the process. Ideally, we want those steps to happen in order; if several elements move all at once, it can be very distracting. However, if adding the new item causes the overall list’s height to change, the effect works best if that animation happens at the same time as the other steps.

jQuery’s animate() function takes an optional duration parameter, which can be specified in milliseconds to define how long the animation runs for. In addition, a callback function can be provided to ensure steps in an animation are carried out in sequence.

.animate( properties, [ duration ], [callback] )

Firstly, we calculate any height difference the item change might cause to the overall list. This is simply the difference in the height of the old and new list items:

var heightDiff = $('#scroller li:first').outerHeight()
    - $('#scroller li:last').outerHeight();

Then, we run two animations in parallel: one for the list height change, and one for the items’ movement. That movement consists of 3 steps (show new item, scroll all items, hide old item) which should, in total, take the same amount of time as the height change. I’ve found the following values to be pleasant but, of course, you can experiment:

$('#scroller').animate({height: h + heightDiff}, 1500);

$('#scroller li:first').animate({top: 0}, 250, function() {
  $('#scroller li:first').animate({marginTop: oldMarginTop}, 1000, 
    function() {
      $('#scroller li:last').animate({top: ulPaddingBottom}, 250, 
        function() {
          $('#scroller li:last').remove();
          $('#scroller').css('height', 'auto');
          $('#scroller').css('overflow', 'visible');
      });
  });
});

And here’s the final effect; each click on the button adds a new item:

The code is wrapper up in a smoothAdd() function which takes a list’s id and text to add within a new list item.

And finally …

The eagle-eyed amongst you might spot one, very small problem with the final effect. For now, I’ll leave that identification as an exercise for the reader, in the knowledge that I’m opening myself up to all manner of problem reports! But I’ll deal with the specific issue that I’ve spotted in a follow-up post shortly.


Tweet

Comments

Mon 8 Feb 2010 05:47

Bartek Stankowski

Bartek Stankowski said:

That’s a nice step-by-step tutorial.

But I think it’s worth saying that you could improve performance and make your code cleaner, with a few simple changes.

First of all, you can apply more than one CSS rule at a time, so instead of writing:

<pre>$('#scroller li:first').css('position', 'relative');

$('#scroller li:first').css('top', 0 — ulPaddingTop);

</pre>

you should do just:

<pre>

$('#scroller li:first').css({

position: 'relative',

top: 0 — ulPaddingTop

})

</pre>

No need to look for $('#scroller li:first') in the DOM so many times.

Also, you could cache DOM nodes in variables, and than find it’s children using context, like this:

<pre>

var $scroller = $('#scroller');

$('li:first', $scroller);

</pre>

It’s faster.

And the last thing. When inside an animation callback function, you can use "this", to animate the same element again.

Instead of:

<pre>

$('#scroller li:first').animate({top: 0}, 250, function() {

$('#scroller li:first').animate({marginTop: oldMarginTop}, 1000, function() {

// (…)

})

})

</pre>

use:

<pre>

$('#scroller li:first').animate({top: 0}, 250, function() {

$(this).animate({marginTop: oldMarginTop}, 1000, function() {

// (…)

})

})

</pre>

Mon 8 Feb 2010 05:51

Bartek Stankowski

Bartek Stankowski said:

Oh and one more thing.

I think it’s easier to use parseInt() instead of substr() to get the padding value:

var ulPaddingBottom = parseInt($('#scroller').css('padding-bottom'));

Cheers.

Mon 8 Feb 2010 06:53

Five Minute Argument

Five Minute Argument said:

@Bartek: Those are great performance tips, thanks — I’ll definitely look into working those into the example. As I stated, the focus was on the step-by-step tutorial, particularly with respect to the CSS positioning, so I didn’t focus on efficiency too much. But, if the animation is not performing well, it should certainly be improved — a smooth movement was one of the requirements, after all!

As an aside, does jQuery not carry out its own caching to speed up repeated selectors? I suppose it’s not really necessary, if you write decent code in the first place ;-)

And good tip on parseInt() — I always forget it can be used in that way.

Mon 8 Feb 2010 11:04

Amber Weinberg

Amber Weinberg said:

Nice tutorial :)

Sat 20 Mar 2010 00:21

J

J said:

If you remove the initial 5 prepends, the script breaks and the content does not stay in place. Any chance of a fix?

Sat 20 Mar 2010 08:02

Five Minute Argument

Five Minute Argument said:

Hi 'J'. Part of the purpose of the script is to work with a fixed number of items, but I can understand a need for the type of behaviour you’re describing. Is it correct to say that you’d like an empty list from the beginning, a number of items to be added as and when they’re available, and a fixed limit on the total number to be displayed?

I’ll put something together and add an update soon.

Fri 26 Mar 2010 12:13

Tom Girling

Tom Girling said:

Thats a great bit of code — thanks…very useful — I need to find a use for it now!

Mon 29 Mar 2010 08:23

Emma

Emma said:

Hi, great tutorial but i have one question. How would I get this to repeat automatically without the button? So the news just scrolls through?

Thank you for your help and a great tut!

Emma F

Tue 3 Aug 2010 20:31

Las Vegas Web Design

Las Vegas Web Design said:

So this is how you do it. I've been looking for articles that talks about how you build a scrolling list with jQuery. Thanks.

Wed 18 Aug 2010 02:19

christian

christian said:

Works very good as stand alone list in an empty html page.

However, trying to put this scrolling list inside some nested divs etc with some images above, (real world scenario), and the scrolling is far from smooth, it is slow and hogs the browser. Also witnessed strange behavior in IE. I’m using the smoothAdd() code, and I add a new element every 5s.

Anyone else got this working inside a real page? and in IE8?

/c

Fri 22 Oct 2010 06:34

Mithilesh

Mithilesh said:

all good scripts but i found the similar one at jqeury.

Sat 6 Nov 2010 05:37

Salomao

Salomao said:

Puts the sorce to dowload with php code, would be very grateful. and also other user would also be very grateful. :)

Wed 22 Dec 2010 01:11

Raghib Suleman

Raghib Suleman said:

cool nice plugin of scroll…

Tue 5 Apr 2011 17:48

Las Vegas Web Design

Las Vegas Web Design said:

Love your information. Thank you for sharing.

Tue 26 Apr 2011 05:37

mawo

mawo said:

thanks for that — thats exactly what I was looking for. how do I have to modify the script to make a new item appear from the bottom and an old one disappear to the top? (upside-down).

Fri 29 Apr 2011 22:49

Adam Dukes

Adam Dukes said:

Nice! Exactly what I was looking for.

Thank you!

Mon 30 May 2011 03:26

ahmad

ahmad said:

thanks alot,

very good tutorial

Wed 30 Nov 2011 22:44

John

John said:

Great simple tutorial with a detailed description , thank you

Wed 28 Dec 2011 00:56

SEO tips

SEO tips said:

Nice article! Thanks for sharing with us!

Tue 24 Jan 2012 04:28

buxum

buxum said:

how do I have to change the program to create a new product appear from the end and an old one fade away to the top?<a rel="dofollow" rel="nofollow" href="http://www.emanprinting.com">cheap sticker printing</a>

Thu 26 Jan 2012 01:02

slub yarn in pakistan

slub yarn in pakistan said:

Thanks for sharing such nice article here.i also want to share valuable how to build a list tips.

Thu 26 Jan 2012 01:04

auto loan ohio

auto loan ohio said:

I was looking for. how do I have to modify the script to make a new item appear from the bottom and an old one disappear to the top? (upside-down).

Thu 26 Jan 2012 01:06

auto loan ohio

auto loan ohio said:

Many similar devices Iâve seen on the web fail on at least one of these requirements, and this was part of the driver towards putting this tutorial together.

Mon 27 Feb 2012 04:04

Oliver Jobson

Oliver Jobson said:

Just built a great application with this, but now I am testing it for release and I get a bug in IE8 and IE7 and which prevent sthe scroll from happening. It works perfectly in Safari, Firefox and Chrome.

IE developer tools highlights a problem with the following code section of the jquery code:

a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now

I have tried 1.40 (dev), 1.6.2 min and 1.7.1 min and all create the issue.

** Found a fix while writing this: involves correcting part of the jquery code in 1.7.1 min: http://bugs.jquery.com/ticket/11361

Thanks,

Mon 27 Feb 2012 04:06

Oliver Jobson

Oliver Jobson said:

Sorry for the spam — I didn’t think it was posting as I was being shown the form populated with my comment info after clicking 'post'.

Hope people find the fix in my last comment useful.

Wed 7 Mar 2012 19:33

Carl Saunders

Carl Saunders said:

Please edit and correct my mistakes below. Here is my current attempt to step through the tutorial, which seems not to recognize jQuery statements:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

<head>

<script type="text/javascript"

src="../ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script>

<link rel="stylesheet"

href="js/jquery-ui-1.7/themes/base/jquery-ui.css"

type="text/css">

<link rel="stylesheet" href="css/scroller.css"

type="text/css">

<script type="text/javascript" src="smooth-add.js"></script>

<title>Scroller Devel</title>

</head>

<body>

<ul id="scroller">

<li> Row One </li>

<li> Row Two </li>

<li> Row Three </li>

<li> Row Four </li>

<li> Row Five </li>

<li> Row Six </li>

</ul>

$('#scroller').prepend('<li> NEXT Line</li>

');

</body>

</html>

Wed 7 Mar 2012 19:36

Carl Saunders

Carl Saunders said:

Please edit and correct my mistakes below. Forgot to mention that I mimic-ed your directory structure on my local disk.

Here is my current attempt to step through the tutorial, which seems not to recognize jQuery statements:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>

<head>

<script type="text/javascript"

src="../ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script>

<link rel="stylesheet"

href="js/jquery-ui-1.7/themes/base/jquery-ui.css"

type="text/css">

<link rel="stylesheet" href="css/scroller.css"

type="text/css">

<script type="text/javascript" src="smooth-add.js"></script>

<title>Scroller Devel</title>

</head>

<body>

<ul id="scroller">

<li> Row One </li>

<li> Row Two </li>

<li> Row Three </li>

<li> Row Four </li>

<li> Row Five </li>

<li> Row Six </li>

</ul>

$('#scroller').prepend('<li> NEXT Line</li>

');

</body>

</html>

Mon 30 Apr 2012 15:45

Muhammadibn

Muhammadibn said:

Hi, Anyone created a wordpress version of this?

Mon 24 Feb 2014 16:50

UK Pay Day Loan

UK Pay Day Loan said:

payday loans for bad credit Loan Interest Ratespayday loans for

bad credit lending organizations are in business to advance

the interests of business, not those of hard-pressed workpeople.

Internet technique doesn t allow yyou to borrow extra money from a cash issue

before it becomes large. Borrowers use the loans to assist you all

thee time. What they understand is that mom says no extra milk on the

cereal or noo second helpings of me.

Leave a comment