Smooth scrolling image list

I had someone ask me about fitting more images in a small area and the way flickr does their image scrolling came to mind. I wanted to see how hard it was to do and as it turns out it isn't hard to do at all thanks to script.aculo.us.

I'm making this as simple as I can to illustrate just how nice the new tools like script.aculo.us are.

Step 1. You need a box and some images. You will also need to know the size of the images or at least the size of the largest one. In an attempt to keep this simple I'm going to assume all the images are the same size.

<div id="imageBox">
  <div id="imageBoxInside">
    <img src="http://www.ioncannon.net/examples/slide/images/turtle_sm_1.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/elephants_sm_1.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/turtle_sm_2.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/elephants_sm_2.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/mages/turtle_sm_3.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/elephants_sm_3.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/turtle_sm_4.jpg" />
    <img src="http://www.ioncannon.net/examples/slide/images/elephants_sm_4.jpg" />
    <br/>
  </div>
</div>

Notice from this code that we have an outer box and an inner box. We will next want to make the outer box smaller than the inner box so that only a few of the images can be seen at one time.

Step 2. To hide the extra images we give the outer box a set width, in this case I want to show only 2 images at a time and each image is 180px wide so I make the outer box 360px wide. Notice that the overflow is hidden. The hidden overflow is what keeps the images that are in the inner box but not within the outer box's width hidden.

I'm using floats to lay the images out one next to the other. Because of this we need to give the inside box a large width so that it will not wrap the floated images.

<style type="text/css">
#imageBox { margin: auto; width: 360px; border: 1px #000 solid; overflow: hidden; }
#imageBoxInside { width: 10000px; }  #imageBox img { float: left; padding: 0px; margin: 0px; }
#imageBox br { clear: both; }
</style>

Step 3. Now for the magic. You need the latest version of script.aculo.us because I use the Effects.Move function and they have changed Effects.MoveBy to Effects.Move only recently.

We now create two functions to move the images ether one step to the right or one step to the left. Each step is the size of a single image so the when one is hit it moves one image out of thew view and another image into view. As you can see from the following code this is extremely easy using the script.aculo.us library.

function moveToPrevious()
{
  new Effect.Move('imageBoxInside', { x: 180, y: 0, transition: Effect.Transitions.sinoidal });
}  

function moveToNext()
{
  new Effect.Move('imageBoxInside', { x: -180, y: 0, transition: Effect.Transitions.sinoidal });
}

Step 4. The only thing that remains is to connect everything together. We add a couple links to move call the next and previous functions defined above.

<a href="javascript:void(0);" href="javascript:void(0);" onclick="moveToPrevious(); return true;"><img src="http://www.ioncannon.net/examples/slide/images/previous.png" /></a>
<a href="javascript:void(0);" href="javascript:void(0);" onclick="moveToNext(); return true;"><img src="http://www.ioncannon.net/examples/slide/images/next.png" /></a>

And that is all there is to it. See it in action!

Tags: ,

34 thoughts on “Smooth scrolling image list

  1. Pingback: IONCANNON » Easier scrolling images with Yahoo UI

  2. Etan

    Very great article! Thx :)

    Could you tell me how can i add a link on every image? The basix idea is to scroll an image list but maybe it could be great to add the ability to click on each image to visit a website.

  3. zeeker

    wonderful! just what i was out for. but what if I wanted to stop the scroll once the pics are over? now it just scrolls to infinity!? even if i reduce the "width".. am i missing something?
    thanks

  4. afx2000

    zeeker: I used the "left" value of the ImageBoxInside to determine the scroll buttons:
    if left is 0, ImageBoxInside is on the very left, so hide left scroll arrow

    if left + width of the ImageBoxInside = width of imageBox, ImageBoxInside is on the very right, so hide right scroll arrow.

    if width of ImageBoxInside

  5. Pragan

    Hi,

    I am new to script.aculo.us so I would like to know how the above script works if I use it in my webpages??Is there any configuration/installion for any script.aculo.us ??or we can copy the reqd functions from the library?I am confused here..Please help me out.

  6. Pragan

    Please ignore my previous comment! i got it worked but I would like to know whether is there any way to display end of gallery if we reach the last image because right now if we keep pressing the right arrow after the last image its just show the white space and we have to refresh the page or click the left arrow for equal number of times we clicked the right arrow..

    Please help me out.

    Thanks
    Pragan

  7. luu tieu duong

    Hi, it's the first time I use scriptaculous, but this article is easy to do. However, there is a problem, when I use DTD XHTML 1.0 Trasitional (-!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"-
    -html xmlns="http://www.w3.org/1999/xhtml"-…), the page is ok in FF, but it is not correct in IE6. When I click on the arrow button the "imageBoxInside" expands horizontally over the "imagebox" (not inside it like FF).
    I tried to cancel the DTD on my page and everything is ok in both FF and IE6. But it affects another problem of the page.
    Please help me solve this problem.

    Thank you so much.
    luutieuduong

  8. EC

    Great article and really nice implementation. The only problem that happens is when you click the scroll arrows quickly several times, the images display out of line. Is there a way to prevent the script from firing until the scroll is complete??

  9. Christofer

    I have the exact same problem as "luutieuduong". Anyone who has a solution?

    The problem is that the Effect.Move changes the style property for imageBoxInside from position: static to position: relative. So in IE6+IE7 this causes the same problem for me as for "luutieuduong". See www(dot)andersroth(dot)se.

  10. Christofer

    Adding "position: relative" to the imageBox-css fixed the problem for me, both in IE6 and IE7.

    I hope this fix will work for anyone else with the same problem!

  11. dandy

    nice article.

    but same problem. the next button moves the div to infinity. any solution?

    regards.

  12. goldenapples

    @EC :

    I had the same problem in a site I was working on. Adding the declaration

    queue: 'end'

    to the effect properties solves that problem. The move will still be recorded, but it won't start until all currently running moves have finished.

  13. miriam

    could someone better explain how correct the scrolling to infinity issue? If goldenapples queue:'end' works, how exactly does it get added to the code?

    Thank you!!!

  14. FG

    In case it may help I have implemented a solution to avoid to scroll beyond the limits of the list (it uses prototype functions) :

    Update the js:

    function moveToPrevious(boxDivId, contentDivId, imageWidth)
    {
    var contentPosition = $(contentDivId).cumulativeOffset();
    var boxPosition = $(boxDivId).cumulativeOffset();

    if (contentPosition[0] (boxPosition[0] + boxWidth)) {
    new Effect.Move(contentDivId, { x: -imageWidth, y: 0, transition: Effect.Transitions.sinoidal });
    }
    }

    Replace css by:

    #imageBox { margin: auto; overflow: hidden; position: relative;}

    Here is an example of the html:

    prev
    next

    Note that in this example, images are 400 px wide.

  15. Brett

    Hi

    Thanks firstly to everyone involved in creating and updating this script to make it very useful.
    I'm just wondering if anyone sorted out the issue where an impatient user clicks multiple times on the Next or Previous buttons and it sort of breaks the scrolling. The images don't scroll all the way to the beginning or end.

    I noticed EC had the same question above but I'm not sure I can see any solutions. Any help would be greatly appreciated.

    Kind Regards
    Brett

  16. Daniel

    Hi!

    Where have you all put the different codes. I have one html-document and one css-document.

    However the boxes does'nt work, you can see all the images all the time!

    Thanks fore the scripts btw!

  17. Daniel

    I have fixed the problem!

    The only thing that does not work is that the scrolling does not have an end.

    I did not understand FGs sollution.

    Thanks!

  18. Nurhadi

    I have fixed the problem for infinity issue.

    update the js by adding getMoveStatus() function.

    try this!

    /* ——————- */

    var validWBox = 520; /* imageBox width */
    var validWImg = 130; /* image width */
    var numImg = 10; /* count image */

    function getMoveStatus(currMove) {
    var theLeft = document.getElementById('imageBoxInside').style.left;
    /* current left position of ImageBoxInside */

    var imgNumComul = numImg * validWImg;
    /* comulative image width (10*130) */

    theLeft = parseInt(theLeft.replace('px',"));
    if(currMove == 'next') {
    theLeft = theLeft – validWBox;
    } else {
    theLeft = theLeft + validWBox;
    }
    if(theLeft == validWBox) {
    return false;
    } else if(theLeft <= (0-imgNumComul)) {
    return false;
    } else {
    return true;
    }
    }

    /* ——————- */
    function moveToPrevious() {
    var validMove = getMoveStatus('prev');
    if(validMove) {
    new Effect.Move('imageBoxInside', { x: validWBox, y: 0, transition: Effect.Transitions.sinoidal });
    }
    }

    function moveToNext() {
    var validMove = getMoveStatus('next');
    if(validMove) {
    new Effect.Move('imageBoxInside', { x: (0-validWBox), y: 0, transition: Effect.Transitions.sinoidal });
    }
    }

    /* ——————- */

    Prev | Next

    I home it's usefull…

  19. Tom

    thx nurhadi for your code, it looks good.
    but FF told me "moveToNext is not defined"

    When I re-use old function moveToNext as

    function moveToPrevious() {
    new Effect.Move('imageBoxInside', { x: 300, y: 0, transition: Effect.Transitions.sinoidal });
    }

    it's working with the bug of infinity ..
    any ideas ?
    thx a lot

  20. Tom

    Be carreful on this line:
    theLeft = parseInt(theLeft.replace('px',"));

    the " should be replaced by "
    or, the js bug.

  21. Tom

    there's another bug on line :
    theLeft = theLeft + validWBox;

    the JS makes a concatenation of both var, instead of add them.

    good luck :)

  22. eng. Ilian Iliev

    Hi all,

    I create a JS class based on this example and I`ll be glad to hear(see)
    what you think. Sorry for pasting so much code that way but please take
    a look.

    The scroller is called like that:
    var my_scrolltest = new Scroller('imageBox', 'imageBoxInside', 'scroll_left', 'scroll_right', 202);

    3rd and 4th params are links id`s, last one (step) is the scroll step i pixels,
    class source bellow:

    Scroller = Class.create({
    initialize: function(box, innerBox, el_left, el_right, step) {
    var scroller = this;

    this.box = $(box);
    this.handle = $(innerBox);
    this.el_left = $(el_left);
    this.el_right = $(el_right);
    this.step = step;
    this.currentOffsetLeft = 0;

    this.eventLeftScrollOnClick = this.ScrollLeft.bindAsEventListener(this);
    this.eventRightScrollOnClick = this.ScrollRight.bindAsEventListener(this);

    this.el_left.observe("click", this.eventLeftScrollOnClick);
    this.el_right.observe("click", this.eventRightScrollOnClick);

    this.initialized = true;
    },
    ScrollLeft: function(event){
    if (Event.isLeftClick(event)) {
    offset_left = this.currentOffsetLeft

    if(!offset_left || offset_left>= 0)
    return;

    this.currentOffsetLeft += this.step;
    new Effect.Move(this.handle, { x: this.step, y: 0, transition: Effect.Transitions.sinoidal, queue: 'end'});
    Event.stop(event);
    }
    },
    ScrollRight: function(event){
    if (Event.isLeftClick(event)) {
    box_width = this.getObjStyleProperty(this.box, 'width');
    innerBox_width = this.getObjStyleProperty(this.handle, 'width');

    if(box_width>=(this.currentOffsetLeft)+innerBox_width)
    return;

    this.currentOffsetLeft -= this.step;
    new Effect.Move(this.handle, { x: -this.step, y: 0, transition: Effect.Transitions.sinoidal, queue: 'end'});
    Event.stop(event);
    }
    },
    getObjStyleProperty: function(obj, property){
    offset_left = obj.getStyle(property);
    if(!offset_left)
    return;
    return parseInt(offset_left.replace('px', "));
    }
    });

    Any ideas will be appreciated

  23. LD

    How to configure if my scroll buttons are positioned at the beginning and end of the box holding all images? Right now, it scrolls even the buttons.

  24. eng. Ilian Iliev

    @niranjan

    You can just wrap each img element inside a div, example:

    my info text

    ……………

    Hope this helps )

  25. niranjan

    thx "eng. Ilian Iliev" for reply but it doesnt seems to work i had tried to encapsulate the img in a div and added some text but result is something like ladder steps images are displayed like steps of ladder. any thing else i could try?

  26. eng. Ilian Iliev

    Hi niranjan,
    sad to hear it`s not working for you.
    To skip the flooding of Carson`s blog (10x for the post btw) just send me a link or the code you are working on at azareth(at)mail.bg I`ll try to review it as soon as possible.

  27. Justin

    First off, thanks for the code and ideas.

    I slightly modified the code to not go on to infinity either way.
    I created the following global variables:
    var numVisible = 4; //Number of images that are always visible
    var totalImages = 5; // Total Number of images
    var numPossibleMoves = totalImages-numVisible; //The number of possible moves
    var numMoves = 0; //The number of moves taken.

    I then modified the following functions:
    function moveToPrevious()
    {
    if (numMoves > 0)
    {
    numMoves–;
    new Effect.Move('imageBoxInside', { x: 200, y: 0, transition: Effect.Transitions.sinoidal });
    }
    }
    function moveToNext()
    {
    if (numMoves < numPossibleMoves)
    {
    //Then we can still move
    numMoves++;
    new Effect.Move('imageBoxInside', { x: -200, y: 0, transition: Effect.Transitions.sinoidal });
    }
    }

    Hope this helps.

    Justin http://www.jcpsnowproductons.com

Leave a Reply

Your email address will not be published. Required fields are marked *