/** * Creates and starts the item scroller which must receive the ID of the node containing the elements to be scrolled, * an optional increment size for the scroll (may be zero if auto), and an optional autoscroll time interval (millseconds). * Transition is the number of milliseconds for the animation of shifting one increment. * @param viewport The jquery wrapper around the viewport container. * @param increment The number of pixels to shift. If not defined then it will be pulled from the first child of the page. * @param autoTimer The number of milliseconds to wait between automatic shifts. If undefined or <= 0 then there won't be any auto shifts. * @param initialDelay The number of milliseconds to wait before starting automatic shifts, or zero if undefined or less than zero. * @param transition The number of milliseconds to animate the shifting of the page. Will default to 1000. * @param resetPosition Whether the scroll position is reset upon releasing the item scroller. This defaults to true. */ function ItemScroller(viewport, increment, autoTimer, initialDelay, transition, resetPosition) { this.viewport = viewport; this.increment = increment != undefined && increment != 0 ? this.stripMetric(increment) : undefined; if(resetPosition != undefined) { this.resetPosition = resetPosition; } this.init(); if(autoTimer != undefined) { this.autoscroll(autoTimer, initialDelay); } if(transition != undefined && transition > 0) { this.transition = transition; } } ItemScroller.prototype.constructor = ItemScroller; ItemScroller.prototype.viewport = null; ItemScroller.prototype.page = null; ItemScroller.prototype.increment = null; ItemScroller.prototype.pageWidth = null; ItemScroller.prototype.isShifting = false; ItemScroller.prototype.autoscrollPause = false; ItemScroller.prototype.isReleased = false; ItemScroller.prototype.transition = 1000; ItemScroller.prototype.copyCount = 0; ItemScroller.prototype.resetPosition = true; ItemScroller.prototype.currentIndex = 0; ItemScroller.prototype.length = 0; ItemScroller.prototype.init = function() { this.page = this.viewport.children(':first'); var children = this.page.children(); this.length = children.length; //Calculate the increment size for each left/right scroll.// if(this.increment == undefined) { this.increment = this.stripMetric(children.first().outerWidth()); } //Calculate the width of the original set of displayed elements.// var viewportWidth = this.viewport.outerWidth(); var pageWidth = 0; this.page.children().each(function() { pageWidth += $(this).outerWidth(); }); this.pageWidth = pageWidth; //Calculate the wrap size that will be needed on the tail of the original set of elements to simulate a wrap around effect.// var remainder = this.pageWidth / this.increment != 0 ? this.pageWidth % this.increment : this.increment; var tailLength = remainder; //Clone children until we have enough to properly simulate a wrap around scrolling action.// for(var index = 0; index < children.length && tailLength < viewportWidth; index++) { var next = $(children[index]); tailLength += next.outerWidth(); next.clone(true, true).appendTo(this.page); this.copyCount++; } //Now: Fix the spacing the browser adds between inline elements that have any kind of spacing or line feeds between them (bad browsers!).// children = this.page.children(); this.page.css({position: 'relative'}); var offset = 0; children.each(function() { var next = $(this); next.css({position: 'absolute', left: offset, top: 0, display: 'block'}); offset += next.outerWidth(); }); if(this.resetPosition) { //Start at the beginning.// this.viewport.scrollLeft(0); } else { var lastIndex = this.viewport.data('lastIndex'); if(lastIndex) { this.currentIndex = lastIndex; this.viewport.parents().css('display', 'block'); this.viewport.scrollLeft(lastIndex * this.increment); //this.viewport.one('scroll', function() { // this.viewport.scrollLeft(this.currentIndex * this.increment); //}); } else { this.viewport.scrollLeft(0); } } } ItemScroller.prototype.release = function() { //this.page.stop(true, false); //this.page.css({left: 0}); if(!this.released) { //Stop the animation.// this.viewport.stop(true, false); //Remove the children that were added upon initialization.// var children = this.page.children(); children.slice(children.length - this.copyCount).detach(); //Flag ourselves as released.// this.isReleased = true; if(this.resetPosition) { //Start at the beginning.// //this.viewport.scrollLeft(0); } this.viewport.data('lastIndex', this.currentIndex); } } ItemScroller.prototype.next = function(fn) { if(!this.isShifting) { var pageLeft = this.viewport.scrollLeft(); var newPageLeft = pageLeft + this.increment; var wrapPosition = this.pageWidth; var shift = this.pageWidth; var _this = this; this.isShifting = true; //this.viewport.data('nextScrollLeft', newPageLeft); this.currentIndex = (this.currentIndex + 1) % this.length; this.viewport.animate({scrollLeft: newPageLeft}, {duration: this.transition, queue: true, step: function(now, fx) { if(fx.now >= wrapPosition) { fx.start -= shift; fx.end -= shift; fx.now -= shift; } }, complete: function() { _this.isShifting = false; if(fn) fn(); }}); } return this; } ItemScroller.prototype.prev = function(fn) { if(!this.isShifting) { var pageLeft = this.viewport.scrollLeft(); var newPageLeft = pageLeft - this.increment; var wrapPosition = 0; var shift = this.pageWidth; var _this = this; this.isShifting = true; this.currentIndex = this.currentIndex - 1 < 0 ? this.length : this.currentIndex - 1; this.viewport.animate({scrollLeft: newPageLeft}, {duration: this.transition, queue: true, step: function(now, fx) { if(fx.now <= wrapPosition) { fx.start += shift; fx.end += shift; fx.now += shift; } }, complete: function() {_this.isShifting = false; if(fn) fn();}}); } return this; } ItemScroller.prototype.pause = function() { this.autoscrollPause = true; return this; } ItemScroller.prototype.resume = function() { this.autoscrollPause = false; return this; } ItemScroller.prototype.autoscroll = function(delay, initialDelay) { var _this = this; /* Pause scrolling on mouse over. this.page.mouseover(function(eventObject) { _this.autoscrollPause = true; }); this.page.mouseout(function(eventObject) { _this.autoscrollPause = false; }); */ if(initialDelay == undefined || initialDelay <= 0) { this.autoscrollIncrement(delay); } else { window.setTimeout(function() {_this.autoscrollIncrement(delay);}, initialDelay); } } ItemScroller.prototype.autoscrollIncrement = function(delay) { var _this = this; if(!this.isReleased) { var fn = function() { window.setTimeout(function() {_this.autoscrollIncrement(delay);}, delay); }; if(!this.autoscrollPause) { this.next(fn); } else { fn(); } } } ItemScroller.prototype.stripMetric = function(value) { return parseInt(value); }