Quickly Hacking Smooth Div Scroll to Run Vertically
Smooth Div Scroll has a lot to recommend it, but like many carousels it is horizontal only. I recently had the need to build a slow and smooth continuously scrolling wrap-around carousel that runs vertically, and Smooth Div Scroll was one of the releases I looked at. The technique it uses - jQuery's scrollLeft() - seems to be one of the better options for slower continuous scrolling. Javascript in general is not good at all at slow DOM animations, which appear increasingly jagged and jerky the slower they get.
But if you can find a speed that suits, then Javascript is probably a faster development project for old school web developers who just want to shift pieces of the DOM around - mixed text and imagery in moving lists, for example. One alternative to that standard Javascript and DOM approach is to run up something in canvas, for example, which would mean a whole different approach to the content inside the carousel. The results should be much better, however.
Anyway, to return to the point at hand: I quickly hacked a vertical scroll version of Smooth Div Scroll in order to give it a try. The result for slow scrolling is better than, say, the jQuery Cycle plugin, but it still starts to look jaggy in modern webkit and Firefox browsers with these parameters:
jQuery("#scroller").smoothDivScroll({ autoScrollingMode: "always", autoScrollingDirection: "endlessloopbottom", autoScrollingStep: 1, autoScrollingInterval: 100 });
Since Smooth Div Scroll is under the GPL, here is a release of the quick hack job I did on it - the only changes being to the two main files. The caveats are that this was for a use without hotspots or any of the other interesting bells and whistles, so those items may still need various height- and width-related items swapped around. I also pulled out the content replacement / AJAX functionality as I had no use for it. The parameters remain much the same - just replace "left" with "top" and "right" with "bottom" and you're good to go.
smoothDivScroll.css
div.scrollableArea { position: relative; width: 100%; height: auto; }
jquery.smoothDivScroll-1.2.js
/* * jQuery SmoothDivScroll 1.2 * * Copyright (c) 2012 Thomas Kahn * Licensed under the GPL license. * * http://www.smoothdivscroll.com/ * * Depends: * jquery-1.7.x.min.js * Please use //ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js * * jquery.ui.widget.js * jquery.ui.effects.min.js * Make your own custom download at http://jqueryui.com/download. * First deslect all components. Then check just "Widget" and "Effects Core". * Download the file and put it in your javascript folder. * * jquery.mousewheel.min.js * Download the latest version at http://brandonaaron.net/code/mousewheel/demos * */ (function ($) { $.widget("thomaskahn.smoothDivScroll", { // Default options options: { // Classes for elements added by Smooth Div Scroll scrollingHotSpotTopClass: "scrollingHotSpotTop", // String scrollingHotSpotBottomClass: "scrollingHotSpotBottom", // String scrollableAreaClass: "scrollableArea", // String scrollWrapperClass: "scrollWrapper", // String // Misc settings hiddenOnStart: false, // Boolean ajaxContentURL: "", // String countOnlyClass: "", // String startAtElementId: "", // String // Hotspot scrolling hotSpotScrolling: true, // Boolean hotSpotScrollingStep: 15, // Pixels hotSpotScrollingInterval: 10, // Milliseconds hotSpotMouseDownSpeedBooster: 3, // Integer visibleHotSpotBackgrounds: "onstart", // always, onstart or empty (no visible hotspots) hotSpotsVisibleTime: 5000, // Milliseconds easingAfterHotSpotScrolling: true, // Boolean easingAfterHotSpotScrollingDistance: 10, // Pixels easingAfterHotSpotScrollingDuration: 300, // Milliseconds easingAfterHotSpotScrollingFunction: "easeOutQuart", // String // Mousewheel scrolling mousewheelScrolling: false, // Boolean mousewheelScrollingStep: 70, // Pixels easingAfterMouseWheelScrolling: true, // Boolean easingAfterMouseWheelScrollingDuration: 300, // Milliseconds easingAfterMouseWheelScrollingFunction: "easeOutQuart", // String // Manual scrolling (hotspot and/or mousewheel scrolling) manualContinuousScrolling: false, // Boolean // Autoscrolling autoScrollingMode: "", // String autoScrollingDirection: "endlessloopbottom", // String autoScrollingStep: 1, // Pixels autoScrollingInterval: 10, // Milliseconds // Easing for when the scrollToElement method is used scrollToAnimationDuration: 1000, // Milliseconds scrollToEasingFunction: "easeOutQuart" // String }, _create: function () { var self = this, o = this.options, el = this.element; // Create additional elements needed by the plugin // First the wrappers el.wrapInner("<div class='" + o.scrollableAreaClass + "'>").wrapInner("<div class='" + o.scrollWrapperClass + "'>"); // Then the hot spots el.prepend("<div class='" + o.scrollingHotSpotTopClass + "'></div><div class='" + o.scrollingHotSpotBottomClass + "'></div>"); // Create variables in the element data storage el.data("scrollWrapper", el.find("." + o.scrollWrapperClass)); el.data("scrollingHotSpotBottom", el.find("." + o.scrollingHotSpotBottomClass)); el.data("scrollingHotSpotTop", el.find("." + o.scrollingHotSpotTopClass)); el.data("scrollableArea", el.find("." + o.scrollableAreaClass)); el.data("speedBooster", 1); el.data("scrollYPos", 0); el.data("hotSpotHeight", el.data("scrollingHotSpotTop").innerHeight()); el.data("scrollableAreaHeight", 0); el.data("startingPosition", 0); el.data("bottomScrollingInterval", null); el.data("topScrollingInterval", null); el.data("autoScrollingInterval", null); el.data("hideHotSpotBackgroundsInterval", null); el.data("previousScrollTop", 0); el.data("pingPongDirection", "bottom"); el.data("getNextElementHeight", true); el.data("swapAt", null); el.data("startAtElementHasNotPassed", true); el.data("swappedElement", null); el.data("originalElements", el.data("scrollableArea").children(o.countOnlyClass)); el.data("visible", true); el.data("enabled", true); el.data("scrollableAreaHeight", el.data("scrollableArea").height()); el.data("scrollerOffset", el.offset()); el.data("initialAjaxContentLoaded", false); /***************************************** SET UP EVENTS FOR SCROLLING RIGHT *****************************************/ // Check the mouse Y position and calculate // the relative Y position inside the bottom hotspot el.data("scrollingHotSpotBottom").bind("mousemove", function (e) { var y = e.pageY - (this.offsetTop + el.data("scrollerOffset").top); el.data("scrollYPos", Math.round((y / el.data("hotSpotHeight")) * o.hotSpotScrollingStep)); if (el.data("scrollYPos") === Infinity) { el.data("scrollYPos", 0); } }); // Mouseover bottom hotspot - scrolling el.data("scrollingHotSpotBottom").bind("mouseover", function () { // Stop any ongoing animations el.data("scrollWrapper").stop(true, false); // Stop any ongoing autoscrolling self.stopAutoScrolling(); // Start the scrolling interval el.data("bottomScrollingInterval", setInterval(function () { if (el.data("scrollYPos") > 0 && el.data("enabled")) { el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + (el.data("scrollYPos") * el.data("speedBooster"))); if (o.manualContinuousScrolling) { self._checkContinuousSwapBottom(); } self._showHideHotSpots(); } }, o.hotSpotScrollingInterval)); // Callback self._trigger("mouseOverBottomHotSpot"); }); // Mouseout bottom hotspot - stop scrolling el.data("scrollingHotSpotBottom").bind("mouseout", function () { clearInterval(el.data("bottomScrollingInterval")); el.data("scrollYPos", 0); // Easing out after scrolling if (o.easingAfterHotSpotScrolling && el.data("enabled")) { el.data("scrollWrapper").animate({ scrollTop: el.data("scrollWrapper").scrollTop() + o.easingAfterHotSpotScrollingDistance }, { duration: o.easingAfterHotSpotScrollingDuration, easing: o.easingAfterHotSpotScrollingFunction }); } }); // mousedown bottom hotspot (add scrolling speed booster) el.data("scrollingHotSpotBottom").bind("mousedown", function () { el.data("speedBooster", o.hotSpotMouseDownSpeedBooster); }); // mouseup anywhere (stop boosting the scrolling speed) $("body").bind("mouseup", function () { el.data("speedBooster", 1); }); /***************************************** SET UP EVENTS FOR SCROLLING LEFT *****************************************/ // Check the mouse Y position and calculate // the relative Y position inside the top hotspot el.data("scrollingHotSpotTop").bind("mousemove", function (e) { var y = ((this.offsetTop + el.data("scrollerOffset").top + el.data("hotSpotHeight")) - e.pageY); el.data("scrollYPos", Math.round((y / el.data("hotSpotHeight")) * o.hotSpotScrollingStep)); if (el.data("scrollYPos") === Infinity) { el.data("scrollYPos", 0); } }); // Mouseover top hotspot el.data("scrollingHotSpotTop").bind("mouseover", function () { // Stop any ongoing animations el.data("scrollWrapper").stop(true, false); // Stop any ongoing autoscrolling self.stopAutoScrolling(); el.data("topScrollingInterval", setInterval(function () { if (el.data("scrollYPos") > 0 && el.data("enabled")) { el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() - (el.data("scrollYPos") * el.data("speedBooster"))); if (o.manualContinuousScrolling) { self._checkContinuousSwapTop(); } self._showHideHotSpots(); } }, o.hotSpotScrollingInterval)); // Callback self._trigger("mouseOverTopHotSpot"); }); // mouseout top hotspot el.data("scrollingHotSpotTop").bind("mouseout", function () { clearInterval(el.data("topScrollingInterval")); el.data("scrollYPos", 0); // Easing out after scrolling if (o.easingAfterHotSpotScrolling && el.data("enabled")) { el.data("scrollWrapper").animate({ scrollTop: el.data("scrollWrapper").scrollTop() - o.easingAfterHotSpotScrollingDistance }, { duration: o.easingAfterHotSpotScrollingDuration, easing: o.easingAfterHotSpotScrollingFunction }); } }); // mousedown top hotspot (add scrolling speed booster) el.data("scrollingHotSpotTop").bind("mousedown", function () { el.data("speedBooster", o.hotSpotMouseDownSpeedBooster); }); /***************************************** SET UP EVENT FOR MOUSEWHEEL SCROLLING *****************************************/ el.data("scrollableArea").mousewheel(function (event, delta) { if (el.data("enabled") && o.mousewheelScrolling) { event.preventDefault(); // Stop any ongoing autoscrolling if it's running self.stopAutoScrolling(); // Can be either positive or negative var pixels = Math.round(o.mousewheelScrollingStep * delta); self.move(pixels); } }); // Capture and disable mousewheel events when the pointer // is over any of the hotspots if (o.mousewheelScrolling) { el.data("scrollingHotSpotTop").add(el.data("scrollingHotSpotBottom")).mousewheel(function (event, delta) { event.preventDefault(); }); } /***************************************** SET UP EVENT FOR RESIZING THE BROWSER WINDOW *****************************************/ $(window).bind("resize", function () { self._showHideHotSpots(); self._trigger("windowResized"); }); /***************************************** FETCHING AJAX CONTENT ON INITIALIZATION *****************************************/ // If there's an ajaxContentURL in the options, // fetch the content if (o.ajaxContentURL.length > 0) { self.changeContent(o.ajaxContentURL, "", "html", "replace"); } else { self.recalculateScrollableArea(); } // If the user wants to have visible hotspot backgrounds, // here is where it's taken care of if (o.autoScrollingMode !== "always") { switch (o.visibleHotSpotBackgrounds) { case "always": self.showHotSpotBackgrounds(); break; case "onstart": self.showHotSpotBackgrounds(); el.data("hideHotSpotBackgroundsInterval", setTimeout(function () { self.hideHotSpotBackgrounds("slow"); }, o.hotSpotsVisibleTime)); break; default: break; } } // Should it be hidden on start? if (o.hiddenOnStart) { self.hide(); } /***************************************** AUTOSCROLLING *****************************************/ // The $(window).load event handler is used because the height of the // elements are not calculated properly until then, at least not in Google Chrome. // The autoscrolling // is started here as well for the same reason. If the autoscrolling is // not started in $(window).load, it won't start because it will interpret // the scrollable areas as too short. $(window).load(function () { // Recalculate if it's not hidden if (!(o.hiddenOnStart)) { self.recalculateScrollableArea(); } // Autoscrolling is active if ((o.autoScrollingMode.length > 0) && !(o.hiddenOnStart)) { self.startAutoScrolling(); } }); }, /********************************************************** Override _setOption and handle altered options **********************************************************/ _setOption: function (key, value) { var self = this, o = this.options, el = this.element; // Update option o[key] = value; if (key === "hotSpotScrolling") { // Handler if the option hotSpotScrolling is altered if (value === true) { self._showHideHotSpots(); } else { el.data("scrollingHotSpotTop").hide(); el.data("scrollingHotSpotBottom").hide(); } } else if (key === "autoScrollingStep" || // Make sure that certain values are integers, otherwise // they will summon bad spirits in the plugin key === "easingAfterHotSpotScrollingDistance" || key === "easingAfterHotSpotScrollingDuration" || key === "easingAfterMouseWheelScrollingDuration") { o[key] = parseInt(value, 10); } else if (key === "autoScrollingInterval") { // Handler if the autoScrollingInterval is altered o[key] = parseInt(value, 10); self.startAutoScrolling(); } }, /********************************************************** Hotspot functions **********************************************************/ showHotSpotBackgrounds: function (fadeSpeed) { // Alter the CSS (SmoothDivScroll.css) if you want to customize // the look'n'feel of the visible hotspots var self = this, el = this.element; // Fade in the hotspot backgrounds if (fadeSpeed !== undefined) { // Before the fade-in starts, we need to make sure the opacity is zero el.data("scrollingHotSpotTop").add(el.data("scrollingHotSpotBottom")).css("opacity", "0.0"); el.data("scrollingHotSpotTop").addClass("scrollingHotSpotTopVisible"); el.data("scrollingHotSpotBottom").addClass("scrollingHotSpotBottomVisible"); // Fade in the hotspots el.data("scrollingHotSpotTop").add(el.data("scrollingHotSpotBottom")).fadeTo(fadeSpeed, 0.35); } // Don't fade, just show them else { // The top hotspot el.data("scrollingHotSpotTop").addClass("scrollingHotSpotTopVisible"); el.data("scrollingHotSpotTop").removeAttr("style"); // The bottom hotspot el.data("scrollingHotSpotBottom").addClass("scrollingHotSpotBottomVisible"); el.data("scrollingHotSpotBottom").removeAttr("style"); } self._showHideHotSpots(); }, hideHotSpotBackgrounds: function (fadeSpeed) { var el = this.element; // Fade out the hotspot backgrounds if (fadeSpeed !== undefined) { // Fade out the top hotspot el.data("scrollingHotSpotTop").fadeTo(fadeSpeed, 0.0, function () { el.data("scrollingHotSpotTop").removeClass("scrollingHotSpotTopVisible"); }); // Fade out the bottom hotspot el.data("scrollingHotSpotBottom").fadeTo(fadeSpeed, 0.0, function () { el.data("scrollingHotSpotBottom").removeClass("scrollingHotSpotBottomVisible"); }); } // Don't fade, just hide them else { el.data("scrollingHotSpotTop").removeClass("scrollingHotSpotTopVisible").removeAttr("style"); el.data("scrollingHotSpotBottom").removeClass("scrollingHotSpotBottomVisible").removeAttr("style"); } }, // Function for showing and hiding hotspots depending on the // offset of the scrolling _showHideHotSpots: function () { var self = this, el = this.element, o = this.options; // If the manual scrolling is set if (o.manualContinuousScrolling && o.hotSpotScrolling) { el.data("scrollingHotSpotTop").show(); el.data("scrollingHotSpotBottom").show(); } // Autoscrolling not set to always and hotspot scrolling enabled else if (o.autoScrollingMode !== "always" && o.hotSpotScrolling) { // If the scrollable area is shorter than the scroll wrapper, both hotspots // should be hidden if (el.data("scrollableAreaHeight") <= (el.data("scrollWrapper").innerHeight())) { el.data("scrollingHotSpotTop").hide(); el.data("scrollingHotSpotBottom").hide(); } // When you can't scroll further top the top scroll hotspot should be hidden // and the bottom hotspot visible. else if (el.data("scrollWrapper").scrollTop() === 0) { el.data("scrollingHotSpotTop").hide(); el.data("scrollingHotSpotBottom").show(); // Callback self._trigger("scrollerTopLimitReached"); // Clear interval clearInterval(el.data("topScrollingInterval")); el.data("topScrollingInterval", null); } // When you can't scroll further bottom // the bottom scroll hotspot should be hidden // and the top hotspot visible else if (el.data("scrollableAreaHeight") <= (el.data("scrollWrapper").innerHeight() + el.data("scrollWrapper").scrollTop())) { el.data("scrollingHotSpotTop").show(); el.data("scrollingHotSpotBottom").hide(); // Callback self._trigger("scrollerBottomLimitReached"); // Clear interval clearInterval(el.data("bottomScrollingInterval")); el.data("bottomScrollingInterval", null); } // If you are somewhere in the middle of your // scrolling, both hotspots should be visible else { el.data("scrollingHotSpotTop").show(); el.data("scrollingHotSpotBottom").show(); } } // If autoscrolling is set to always, there should be no hotspots else { el.data("scrollingHotSpotTop").hide(); el.data("scrollingHotSpotBottom").hide(); } }, // Function for calculating the scroll position of a certain element _setElementScrollPosition: function (method, element) { var self = this, el = this.element, o = this.options, tempScrollPosition = 0; switch (method) { case "first": el.data("scrollYPos", 0); return true; case "start": // Check to see if there is a specified start element in the options // and that the element exists in the DOM if (o.startAtElementId !== "") { if (el.data("scrollableArea").has("#" + o.startAtElementId)) { tempScrollPosition = $("#" + o.startAtElementId).position().top; el.data("scrollYPos", tempScrollPosition); return true; } } return false; case "last": el.data("scrollYPos", (el.data("scrollableAreaHeight") - el.data("scrollWrapper").innerHeight())); return true; case "number": // Check to see that an element number is passed if (!(isNaN(element))) { tempScrollPosition = el.data("scrollableArea").children(o.countOnlyClass).eq(element - 1).position().top; el.data("scrollYPos", tempScrollPosition); return true; } return false; case "id": // Check that an element id is passed and that the element exists in the DOM if (element.length > 0) { if (el.data("scrollableArea").has("#" + element)) { tempScrollPosition = $("#" + element).position().top; el.data("scrollYPos", tempScrollPosition); return true; } } return false; default: return false; } }, /********************************************************** Jumping to a certain element **********************************************************/ jumpToElement: function (jumpTo, element) { var self = this, el = this.element; // Check to see that the scroller is enabled if (el.data("enabled")) { // Get the position of the element to scroll to if (self._setElementScrollPosition(jumpTo, element)) { // Jump to the element el.data("scrollWrapper").scrollTop(el.data("scrollYPos")); // Check the hotspots self._showHideHotSpots(); // Trigger the bottom callback switch (jumpTo) { case "first": self._trigger("jumpedToFirstElement"); break; case "start": self._trigger("jumpedToStartElement"); break; case "last": self._trigger("jumpedToLastElement"); break; case "number": self._trigger("jumpedToElementNumber", null, { "elementNumber": element }); break; case "id": self._trigger("jumpedToElementId", null, { "elementId": element }); break; default: break; } } } }, /********************************************************** Scrolling to a certain element **********************************************************/ scrollToElement: function (scrollTo, element) { var self = this, el = this.element, o = this.options, autoscrollingWasRunning = false; if (el.data("enabled")) { // Get the position of the element to scroll to if (self._setElementScrollPosition(scrollTo, element)) { // Stop any ongoing autoscrolling if (el.data("autoScrollingInterval") !== null) { self.stopAutoScrolling(); autoscrollingWasRunning = true; } // Stop any other running animations // (clear queue but don't jump to the end) el.data("scrollWrapper").stop(true, false); // Do the scolling animation el.data("scrollWrapper").animate({ scrollTop: el.data("scrollYPos") }, { duration: o.scrollToAnimationDuration, easing: o.scrollToEasingFunction, complete: function () { // If autoscrolling was running before, start it again if (autoscrollingWasRunning) { self.startAutoScrolling(); } self._showHideHotSpots(); // Trigger the bottom callback switch (scrollTo) { case "first": self._trigger("scrolledToFirstElement"); break; case "start": self._trigger("scrolledToStartElement"); break; case "last": self._trigger("scrolledToLastElement"); break; case "number": self._trigger("scrolledToElementNumber", null, { "elementNumber": element }); break; case "id": self._trigger("scrolledToElementId", null, { "elementId": element }); break; default: break; } } }); } } }, move: function (pixels) { var self = this, el = this.element, o = this.options; // clear queue, move to end el.data("scrollWrapper").stop(true, true); // Only run this code if it's possible to scroll top or bottom, if ((pixels < 0 && el.data("scrollWrapper").scrollTop() > 0) || (pixels > 0 && el.data("scrollableAreaHeight") > (el.data("scrollWrapper").innerHeight() + el.data("scrollWrapper").scrollTop()))) { if (o.easingAfterMouseWheelScrolling) { el.data("scrollWrapper").animate({ scrollTop: el.data("scrollWrapper").scrollTop() + pixels }, { duration: o.easingAfterMouseWheelScrollingDuration, easing: o.easingAfterMouseWheelFunction, complete: function () { self._showHideHotSpots(); if (o.manualContinuousScrolling) { if (pixels > 0) { self._checkContinuousSwapBottom(); } else { self._checkContinuousSwapTop(); } } } }); } else { el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + pixels); self._showHideHotSpots(); if (o.manualContinuousScrolling) { if (pixels > 0) { self._checkContinuousSwapBottom(); } else { self._checkContinuousSwapTop(); } } } } }, /********************************************************** Recalculate the scrollable area **********************************************************/ recalculateScrollableArea: function () { var tempScrollableAreaHeight = 0, foundStartAtElement = false, o = this.options, el = this.element, self = this; // Add up the total height of all the items inside the scrollable area el.data("scrollableArea").children(o.countOnlyClass).each(function () { // Check to see if the current element in the loop is the one where the scrolling should start if ((o.startAtElementId.length > 0) && (($(this).attr("id")) === o.startAtElementId)) { el.data("startingPosition", tempScrollableAreaHeight); foundStartAtElement = true; } tempScrollableAreaHeight = tempScrollableAreaHeight + $(this).outerHeight(true); }); // If the element with the ID specified by startAtElementId // is not found, reset it if (!(foundStartAtElement)) { el.data("startAtElementId", ""); } // Set the height of the scrollable area el.data("scrollableAreaHeight", tempScrollableAreaHeight); el.data("scrollableArea").height(el.data("scrollableAreaHeight")); // Move to the starting position el.data("scrollWrapper").scrollTop(el.data("startingPosition")); el.data("scrollYPos", el.data("startingPosition")); }, /********************************************************** Stopping, starting and doing the autoscrolling **********************************************************/ stopAutoScrolling: function () { var self = this, el = this.element; if (el.data("autoScrollingInterval") !== null) { clearInterval(el.data("autoScrollingInterval")); el.data("autoScrollingInterval", null); // Check to see which hotspots should be active // in the position where the scroller has stopped self._showHideHotSpots(); self._trigger("autoScrollingStopped"); } }, startAutoScrolling: function () { var self = this, el = this.element, o = this.options; if (el.data("enabled")) { self._showHideHotSpots(); // Stop any running interval clearInterval(el.data("autoScrollingInterval")); el.data("autoScrollingInterval", null); // Callback self._trigger("autoScrollingStarted"); // Start interval el.data("autoScrollingInterval", setInterval(function () { // If the scroller is not visible or // if the scrollable area is shorter than the scroll wrapper // any running autoscroll interval should stop. if (!(el.data("visible")) || (el.data("scrollableAreaHeight") <= (el.data("scrollWrapper").innerHeight()))) { // Stop any running interval clearInterval(el.data("autoScrollingInterval")); el.data("autoScrollingInterval", null); } else { // Store the old scrollTop value to see if the scrolling has reached the end el.data("previousScrollTop", el.data("scrollWrapper").scrollTop()); switch (o.autoScrollingDirection) { case "bottom": el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + o.autoScrollingStep); if (el.data("previousScrollTop") === el.data("scrollWrapper").scrollTop()) { self._trigger("autoScrollingBottomLimitReached"); clearInterval(el.data("autoScrollingInterval")); el.data("autoScrollingInterval", null); self._trigger("autoScrollingIntervalStopped"); } break; case "top": el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() - o.autoScrollingStep); if (el.data("previousScrollTop") === el.data("scrollWrapper").scrollTop()) { self._trigger("autoScrollingTopLimitReached"); clearInterval(el.data("autoScrollingInterval")); el.data("autoScrollingInterval", null); self._trigger("autoScrollingIntervalStopped"); } break; case "backandforth": if (el.data("pingPongDirection") === "bottom") { el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + (o.autoScrollingStep)); } else { el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() - (o.autoScrollingStep)); } // If the scrollTop hasnt't changed it means that the scrolling has reached // the end and the direction should be switched if (el.data("previousScrollTop") === el.data("scrollWrapper").scrollTop()) { if (el.data("pingPongDirection") === "bottom") { el.data("pingPongDirection", "top"); self._trigger("autoScrollingBottomLimitReached"); } else { el.data("pingPongDirection", "bottom"); self._trigger("autoScrollingTopLimitReached"); } } break; case "endlessloopbottom": // Do the autoscrolling el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + o.autoScrollingStep); self._checkContinuousSwapBottom(); break; case "endlesslooptop": // Do the autoscrolling el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() - o.autoScrollingStep); self._checkContinuousSwapTop(); break; default: break; } } }, o.autoScrollingInterval)); } }, _checkContinuousSwapBottom: function () { var self = this, el = this.element, o = this.options; // Get the height of the first element. When it has scrolled out of view, // the element swapping should be executed. A true/false variable is used // as a flag variable so the swapAt value doesn't have to be recalculated // in each loop. if (el.data("getNextElementHeight")) { if ((o.startAtElementId.length > 0) && (el.data("startAtElementHasNotPassed"))) { // If the user has set a certain element to start at, set swapAt // to that element height. This happens once. el.data("swapAt", $("#" + o.startAtElementId).outerHeight(true)); el.data("startAtElementHasNotPassed", false); } else { // Set swapAt to the first element in the scroller el.data("swapAt", el.data("scrollableArea").children(":first").outerHeight(true)); } el.data("getNextElementHeight", false); } // Check to see if the swap should be done if (el.data("swapAt") <= el.data("scrollWrapper").scrollTop()) { el.data("swappedElement", el.data("scrollableArea").children(":first").detach()); el.data("scrollableArea").append(el.data("swappedElement")); var wrapperTop = el.data("scrollWrapper").scrollTop(); el.data("scrollWrapper").scrollTop(wrapperTop - el.data("swappedElement").outerHeight(true)); el.data("getNextElementHeight", true); } }, _checkContinuousSwapTop: function () { var self = this, el = this.element, o = this.options; // Get the height of the first element. When it has scrolled out of view, // the element swapping should be executed. A true/false variable is used // as a flag variable so the swapAt value doesn't have to be recalculated // in each loop. if (el.data("getNextElementHeight")) { if ((o.startAtElementId.length > 0) && (el.data("startAtElementHasNotPassed"))) { el.data("swapAt", $("#" + o.startAtElementId).outerHeight(true)); el.data("startAtElementHasNotPassed", false); } else { el.data("swapAt", el.data("scrollableArea").children(":first").outerHeight(true)); } el.data("getNextElementHeight", false); } // Check to see if the swap should be done if (el.data("scrollWrapper").scrollTop() === 0) { el.data("swappedElement", el.data("scrollableArea").children(":last").detach()); el.data("scrollableArea").prepend(el.data("swappedElement")); el.data("scrollWrapper").scrollTop(el.data("scrollWrapper").scrollTop() + el.data("swappedElement").outerHeight(true)); el.data("getNextElementHeight", true); } }, restoreOriginalElements: function () { var self = this, el = this.element; // Restore the original content of the scrollable area el.data("scrollableArea").html(el.data("originalElements")); self.recalculateScrollableArea(); self.jumpToElement("first"); }, show: function () { var el = this.element; el.data("visible", true); el.show(); }, hide: function () { var el = this.element; el.data("visible", false); el.hide(); }, enable: function () { var el = this.element; // Set enabled to true el.data("enabled", true); }, disable: function () { var self = this, el = this.element; // Clear all running intervals self.stopAutoScrolling(); clearInterval(el.data("bottomScrollingInterval")); clearInterval(el.data("topScrollingInterval")); clearInterval(el.data("hideHotSpotBackgroundsInterval")); // Set enabled to false el.data("enabled", false); }, destroy: function () { var self = this, el = this.element; // Clear all running intervals self.stopAutoScrolling(); clearInterval(el.data("bottomScrollingInterval")); clearInterval(el.data("topScrollingInterval")); clearInterval(el.data("hideHotSpotBackgroundsInterval")); // Remove all element specific events el.data("scrollingHotSpotBottom").unbind("mouseover"); el.data("scrollingHotSpotBottom").unbind("mouseout"); el.data("scrollingHotSpotBottom").unbind("mousedown"); el.data("scrollingHotSpotTop").unbind("mouseover"); el.data("scrollingHotSpotTop").unbind("mouseout"); el.data("scrollingHotSpotTop").unbind("mousedown"); // Remove all elements created by the plugin el.data("scrollingHotSpotBottom").remove(); el.data("scrollingHotSpotTop").remove(); el.data("scrollableArea").remove(); el.data("scrollWrapper").remove(); // Restore the original content of the scrollable area el.html(el.data("originalElements")); // Call the base destroy function $.Widget.prototype.destroy.apply(this, arguments); } }); })(jQuery);