ImageScrOOler class - a javascript / moootools image scroller


by D. Christoff - fragged.org - updated 12/06/2009
You need: mootools 1.2+ and ImageScrOOler.js (in that order).
"compile" your mootools with the assets plugin for mootools-more.

The scrolling div that will hold the data is above, id="scr".

Creating an instance is easy:
var mc, sdata = []; // this should contain the JSON, look at Data control example
window.addEvents({
    domready: function() {
        // setup the class instance, mc is in global scope due to beforeunload...
        mc = new ImageScrOOler(sdata, {
            imagePath: "http://www.webtogs.co.uk/brands75/",
            imageHeight: 85,
            targetElement: $("scr"),
            clickEvent: function(obj) {
                window.location.href = "http://www.webtogs.co.uk" + escape(obj.url);
            },
            showProgress: false
        });
    }
});
You can also control the class externally, doing things such as:

Move or stop the scroller


mc.stop();
mc.move();
mc.options.carousel='true'; (works when moving to right, wraps same images without changing direction)

Change scroller direction, for eg. can use arrow buttons with mouseover or click events


mc.moveDirection = 'right';
mc.moveDirection = 'left';
mc.turn(); (ideally, use this method instead)

Change step


mc.options.moveSteps = 1; - default (slow)
mc.options.moveSteps = 4; - 4px at a time
mc.options.moveSteps = 10; - 10px at a time

Change delay between frames

(needs mc.move() to apply after)
mc.options.moveDelay = 16;mc.move(); - default, 16ms before each frame move
mc.options.moveDelay = 8;mc.move(); - 8ms
mc.options.moveDelay = 32;mc.move(); - 32ms
mc.options.moveDelay = 64;mc.move(); - 64ms

Data control - get new images


newData();
var newData = function(){
    this.stop();
    // load a new set of images and replace existing ones
    this.data = [
        {image: '100.jpg', url: '/Lifeventure/', title: 'Lifeventure'},
        {image: '106.jpg', url: '/Littlelife_/', title: 'Littlelife '},
        {image: '8.jpg', url: '/Lowe_Alpine/', title: 'Lowe Alpine'},
        {image: '16.jpg', url: '/Maglite/', title: 'Maglite'},
        {image: '108.jpg', url: '/Mammut/', title: 'Mammut'},
        {image: '96.jpg', url: '/Marmot/', title: 'Marmot'},
        {image: '30.jpg', url: '/Meindl/', title: 'Meindl'},
        {image: '68.jpg', url: '/Merrell/', title: 'Merrell'}
    ];
    this.loadAssets();
    this.move();
}.bind(mc);

Get end-of-line events (or add your own)


mc.addEvent("complete", function(direction) {
    console.log("hello from events, we just moved all the way to the " + direction);
});

Custom click event with a lightBox


if your JSON has additional properties, like obj.largerImage, you can do something like this:
    ...,
    clickEvent: function(obj) {
        if (obj.largerImage.length > 0) {
            new largerBox(obj.largerImage, {options:values}); // this is pseudo code :)
        }
        else {
            window.location.href = escape(obj.url);
        }
    },
    ...

For all this and more, view the raw class here: /js/ImageScrOOler.js
// class:           ImageScrOOler
// version:         1.62
// dependencies:    mootools 1.2.x
// tested on:       FireFox 3, IE7, Safari 4 (beta), Opera 9.60
// last updated:    10/09/2009 15:57:01
// url:             http://fragged.org/
// author:          dimitar chrostoff 
// authorised use:  modify and use as you deem fit. any link back appreciated :)

var ImageScrOOler = new Class({
    Implements: [Options, Events],
    moveDirection: "right",
    moveOffset: 0,
    options: {
        // set defaults here
        targetElement: $empty,  // you NEED the target element to be an object
        imagePath: "",          // prefix images with this
        imageSpacing: 10,       // margin between scrolling images
        imageOpacity: .8,
        clickEvent: function(clickObj) {
            window.location.href = escape(clickObj.url);
        },     // this can open a url or call
        moveDelay: 16,          // animation delay between frames, in ms
        moveSteps: 1,           // move by number of pixels
        imageHeight: 70,        // default image height
        showProgress: true,     // show a 'status' like layer
        defaultMessage: 'Click on the images below',
        showMessage: ' Show me items from ',
        imageClass: 'cur',      // css class to apply to each image, for example, with cursor: pointer
        EOL: 1000,              // delay after eaching END-OF-LINE in ms
        carousel: !true         // if true, it just recycles images and no EOL is reached, works left to right.
    },
    initialize: function(data, options) {
        // data needs to be an array of JSON like this:
        // var data = [
        //    {image: 'Blowfish.gif', url: '/Blowfish', title: 'Blowfish'},
        //    {image: 'Bronx.gif', url: '/Bronx', title: 'Bronx'} ...
        // ];

        this.setOptions(options);

        if ($type(this.options.targetElement) != "element")
            return false;

        if(!data.length)
            return false;

        this.data = data;
        this.options.targetElement.empty();

        // setup our elements
        if (this.options.showProgress) // progress / mouseover status element, style #moostats via css
            this.stats = new Element("span", {id: 'moostats', html: "Initialising..."}).inject(this.options.targetElement);

        // containing titles layer
        this.container = new Element("div", {
            styles: {
                overflow: "hidden",
                height: this.options.imageHeight,
                visibility: "hidden",
                width: this.options.targetElement.getSize().x,
                float: "left"
            }
        }).inject(this.options.targetElement);

        this.subcontainer = new Element("div", {
            styles: {
                overflow: "hidden",
                height: this.options.imageHeight,
                visibility: "hidden",
                width: this.options.targetElement.getSize().x,
                "white-space": "nowrap"
            }
        }).inject(this.container);

        // load images and start moving.
        this.loadAssets();

        // prepare containers and make visible
        this.container.setStyles({
            visibility: "visible",
            opacity: 0.1
        }).fade(0,1);

        this.subcontainer.addEvents({
            mouseleave: function() {
                this.move();
            }.bind(this),
            mouseenter: function() {
                this.stop();
            }.bind(this)
        }).setStyle("visibility", "visible");

        if (this.options.showProgress)
            this.stats.set("html", this.options.defaultMessage);
        // get going!
        this.stop(); // clear up reload issues
        this.subcontainer.scrollTo(0,0);
        this.move();
    },
    loadAssets: function() {
        // injects images into our containers.
        var _this = this; // can't bind this for the images onload so save a ref.

        this.subcontainer.empty();
        // loop array
        this.data.each(function(el, i) {
            new Asset.image(_this.options.imagePath + el.image, {
                title: el.title,
                "class": "mOOimage",
                onload: function() {
                    if (i < _this.data.length)
                        this.setStyle("margin-right", _this.options.imageSpacing);

                    // add css settings to images here or change / force height.
                    this.set({
                        styles: {
                            height: _this.options.imageHeight
                        },
                        opacity: _this.options.imageOpacity
                    }).addClass(_this.options.imageClass).inject(_this.subcontainer);

                    _this.addImageEvents(this);
                }
            });

            if (_this.options.showProgress) {
                // output how many loaded
                if (i < _this.data.length)
                    _this.stats.set("html", i + ' loaded...');
            }
        }); // each

        this.subcontainer.scrollTo(0,0);
    },
    addImageEvents: function(img) {
        return img.set({
            events: {
                click: function() {
                    if (this.options.clickEvent != null) {
                        // collect some garbage
                        this.stop();
                        img.removeEvents();
                        this.options.clickEvent.run(el);
                    }
                }.bind(this),
                mouseenter: function() {
                    if (this.options.showProgress && img.get("title") != null)
                        this.stats.set("html", " " + img.get("title") + "");
                    img.set("opacity", 1);
                }.bind(this),
                mouseleave: function() {
                    if (this.options.showProgress)
                        this.stats.set("html", this.options.defaultMessage);
                    img.set("opacity", this.options.imageOpacity);
                }.bind(this)
            }
        });
    },
    getSize: function() {
        // based on mootools 1.11 getSize, gets the subcontainer size and scroll data
        this.subSize = {
            'scroll': {'x': this.subcontainer.scrollLeft, 'y': this.subcontainer.scrollTop},
            'size': {'x': this.subcontainer.offsetWidth, 'y': this.subcontainer.offsetHeight},
            'scrollSize': {'x': this.subcontainer.scrollWidth, 'y': this.subcontainer.scrollHeight}
        };
    },
    frame: function() {
        // move one frame in whatever direction and handle stop/turn around.
        if (this.moveDirection == "right") {
            this.getSize();
            this.subcontainer.scrollTo(this.subSize.scroll.x+this.options.moveSteps, 0); // number of pixels

            // carousel -- works left to right only.
            if (this.options.carousel) {
                var first = this.subcontainer.getElements("img.mOOimage")[0];

                if (first) {
                    var firstWidth = first.getStyle("width").toInt() + this.options.imageSpacing;
                    if (this.subSize.scroll.x >= firstWidth + this.moveOffset) {
                        this.moveOffset += firstWidth;

                        this.addImageEvents(first.clone()).inject(this.subcontainer); // moves to end.
                        first.removeEvents().removeClass("mOOimage").setOpacity(0.1);
                    }

                }
                return false;
            }

            this.getSize();
            if (this.subSize.size.x + this.subSize.scroll.x >= this.subSize.scrollSize.x && this.subSize.scroll.x > 0) {
                // reached end of data to right, go left
                this.stop();
                this.fireEvent("complete", this.moveDirection);
                this.moveDirection = "left";

                (function() {
                    this.move();
                }).delay(this.options.EOL, this); // pause at end of line to allow seeing last item
            }


        }
        else {
            this.getSize();
            // C.log(this.subSize.scroll.x, this.options.moveSteps, this.subSize.scroll.x - this.options.moveSteps);
            this.subcontainer.scrollTo(this.subSize.scroll.x-this.options.moveSteps, 0); // move nn pixels

            this.getSize();
            if (this.subSize.scroll.x <= 0) {
                // reached left end of data to left, turn around...
                this.stop();
                this.fireEvent("complete", this.moveDirection);
                this.moveDirection = "right";

                (function() {
                    this.move();
                }).delay(this.options.EOL, this); // pause at end of line to allow seeing last item
            };
        }
    },
    move: function() {
        // drives moving
        this.stop();
        this.moveTimer = (function() {
            this.frame();
        }).periodical(this.options.moveDelay, this);
    },
    stop: function() {
        // stop moving.
        $clear(this.moveTimer);
    },
    turn: function() {
        // swap direction
        this.moveDirection = (this.moveDirection == "right") ? "left" : "right";
        this.subcontainer.getElements("img").filter(function(el) {
            return !el.hasClass("mOOimage"); // remove ghost images
        }).dispose();
    }
}); // end ImageScrOOler class