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);