/** * author Remy Sharp * url http://remysharp.com/tag/marquee * https://remysharp.com/2008/09/10/the-silky-smooth-marquee/ */ (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] - (width*2) : width; } else if (behavior == 'slide') { if (newDir == -1) { r = dir == -1 ? marqueeRedux[marqueeState.widthAxis] : width; } else { r = dir == -1 ? marqueeRedux[marqueeState.widthAxis] - (width*2) : 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('