
 author Remy Sharp
 url httpremysharp.comtagmarquee


(function ($) {
    $.fn.marquee = function (klass) {
        var newMarquee = [],
            last = this.length;

         works out the left or right hand reset position, based on scroll
         behavior, current direction and new direction
        function getReset(newDir, marqueeRedux, marqueeState) {
            var behavior = marqueeState.behavior, width = marqueeState.width, dir = marqueeState.dir;
            var r = 0;
            if (behavior == 'alternate') {
                r = newDir == 1  marqueeRedux[marqueeState.widthAxis] - (width2)  width;
            } else if (behavior == 'slide') {
                if (newDir == -1) {
                    r = dir == -1  marqueeRedux[marqueeState.widthAxis]  width;
                } else {
                    r = dir == -1  marqueeRedux[marqueeState.widthAxis] - (width2)  0;
                }
            } else {
                r = newDir == -1  marqueeRedux[marqueeState.widthAxis]  0;
            }
            return r;
        }

         single thread animation
        function animateMarquee() {
            var i = newMarquee.length,
                marqueeRedux = null,
                $marqueeRedux = null,
                marqueeState = {},
                newMarqueeList = [],
                hitedge = false;
                
            while (i--) {
                marqueeRedux = newMarquee[i];
                $marqueeRedux = $(marqueeRedux);
                marqueeState = $marqueeRedux.data('marqueeState');
                
                if ($marqueeRedux.data('paused') !== true) {
                     TODO read scrollamount, dir, behavior, loops and last from data
                    marqueeRedux[marqueeState.axis] += (marqueeState.scrollamount  marqueeState.dir);

                     only true if it's hit the end
                    hitedge = marqueeState.dir == -1  marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir  -1, marqueeRedux, marqueeState)  marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir  -1, marqueeRedux, marqueeState);
                    
                    if ((marqueeState.behavior == 'scroll' && marqueeState.last == marqueeRedux[marqueeState.axis])  (marqueeState.behavior == 'alternate' && hitedge && marqueeState.last != -1)  (marqueeState.behavior == 'slide' && hitedge && marqueeState.last != -1)) {                        
                        if (marqueeState.behavior == 'alternate') {
                            marqueeState.dir = -1;  flip
                        }
                        marqueeState.last = -1;

                        $marqueeRedux.trigger('stop');

                        marqueeState.loops--;
                        if (marqueeState.loops === 0) {
                            if (marqueeState.behavior != 'slide') {
                                marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir, marqueeRedux, marqueeState);
                            } else {
                                 corrects the position
                                marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir  -1, marqueeRedux, marqueeState);
                            }

                            $marqueeRedux.trigger('end');
                        } else {
                             keep this marquee going
                            newMarqueeList.push(marqueeRedux);
                            $marqueeRedux.trigger('start');
                            marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir, marqueeRedux, marqueeState);
                        }
                    } else {
                        newMarqueeList.push(marqueeRedux);
                    }
                    marqueeState.last = marqueeRedux[marqueeState.axis];

                     store updated state only if we ran an animation
                    $marqueeRedux.data('marqueeState', marqueeState);
                } else {
                     even though it's paused, keep it in the list
                    newMarqueeList.push(marqueeRedux);                    
                }
            }

            newMarquee = newMarqueeList;
            
            if (newMarquee.length) {
                setTimeout(animateMarquee, 25);
            }            
        }
        
         TODO consider whether using .html() in the wrapping process could lead to loosing predefined events...
        this.each(function (i) {
            var $marquee = $(this),
                width = $marquee.attr('width')  $marquee.width(),
                height = $marquee.attr('height')  $marquee.height(),
                $marqueeRedux = $marquee.after('div ' + (klass  'class=' + klass + ' '  '') + 'style=display block-inline; width ' + width + 'px; height ' + height + 'px; overflow hidden;div style=float left; white-space nowrap;' + $marquee.html() + 'divdiv').next(),
                marqueeRedux = $marqueeRedux.get(0),
                hitedge = 0,
                direction = ($marquee.attr('direction')  'left').toLowerCase(),
                marqueeState = {
                    dir  downright.test(direction)  -1  1,
                    axis  leftright.test(direction)  'scrollLeft'  'scrollTop',
                    widthAxis  leftright.test(direction)  'scrollWidth'  'scrollHeight',
                    last  -1,
                    loops  $marquee.attr('loop')  -1,
                    scrollamount  $marquee.attr('scrollamount')  this.scrollAmount  2,
                    behavior  ($marquee.attr('behavior')  'scroll').toLowerCase(),
                    width  leftright.test(direction)  width  height
                };
            
             corrects a bug in Firefox - the default loops for slide is -1
            if ($marquee.attr('loop') == -1 && marqueeState.behavior == 'slide') {
                marqueeState.loops = 1;
            }

            $marquee.remove();
            
             add padding
            if (leftright.test(direction)) {
                $marqueeRedux.find(' div').css('padding', '0 ' + width + 'px');
            } else {
                $marqueeRedux.find(' div').css('padding', height + 'px 0');
            }
            
             events
            $marqueeRedux.bind('stop', function () {
                $marqueeRedux.data('paused', true);
            }).bind('pause', function () {
                $marqueeRedux.data('paused', true);
            }).bind('start', function () {
                $marqueeRedux.data('paused', false);
            }).bind('unpause', function () {
                $marqueeRedux.data('paused', false);
            }).data('marqueeState', marqueeState);  finally store the state
            
             todo - rerender event allowing us to do an ajax hit and redraw the marquee

            newMarquee.push(marqueeRedux);

            marqueeRedux[marqueeState.axis] = getReset(marqueeState.dir, marqueeRedux, marqueeState);
            $marqueeRedux.trigger('start');
            
             on the very last marquee, trigger the animation
            if (i+1 == last) {
                animateMarquee();
            }
        });            

        return $(newMarquee);
    };
}(jQuery));

