Skip to content

Instantly share code, notes, and snippets.

@floatplane
Forked from remy/gist:2484402
Last active August 18, 2023 13:20
Show Gist options
  • Save floatplane/6226786 to your computer and use it in GitHub Desktop.
Save floatplane/6226786 to your computer and use it in GitHub Desktop.
jquery marquee plugin - forked to support percentage widths on marquee element, AMD wrapped
/**
* author Remy Sharp
* url http://remysharp.com/tag/marquee
*/
require(['jquery'], 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(),
widthWithUnits = (typeof(width) == 'string' ? width : width + 'px'),
heightWithUnits = (typeof(height) == 'string' ? height : height + 'px'),
$marqueeRedux = $marquee.after('<div ' + (klass ? 'class="' + klass + '" ' : '') + 'style="display: block-inline; width: ' + widthWithUnits + '; height: ' + heightWithUnits + '; overflow: hidden;"><div style="float: left; white-space: nowrap;">' + $marquee.html() + '</div></div>').next(),
marqueeRedux = $marqueeRedux.get(0),
hitedge = 0,
direction = ($marquee.attr('direction') || 'left').toLowerCase(),
marqueeState = {
dir : /down|right/.test(direction) ? -1 : 1,
axis : /left|right/.test(direction) ? 'scrollLeft' : 'scrollTop',
widthAxis : /left|right/.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 : /left|right/.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 (/left|right/.test(direction)) {
$marqueeRedux.find('> div').css('padding', '0 ' + widthWithUnits);
} else {
$marqueeRedux.find('> div').css('padding', heightWithUnits + ' 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);
};
});
@yoopernc
Copy link

yoopernc commented Aug 1, 2014

Any thoughts on what would be needed to handle the "onfinish" event? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment