(function ($) { $.fn.customScrollbar = function (options, args) { var defaultOptions = { skin: undefined, hScroll: true, vScroll: true, updateOnWindowResize: false, animationSpeed: 300, onCustomScroll: undefined, swipeSpeed: 1, wheelSpeed: 40, fixedThumbWidth: undefined, fixedThumbHeight: undefined, preventDefaultScroll: false } var Scrollable = function (element, options) { this.$element = $(element); this.options = options; this.addScrollableClass(); this.addSkinClass(); this.addScrollBarComponents(); if (this.options.vScroll) this.vScrollbar = new Scrollbar(this, new VSizing()); if (this.options.hScroll) this.hScrollbar = new Scrollbar(this, new HSizing()); this.$element.data("scrollable", this); this.initKeyboardScrolling(); this.bindEvents(); } Scrollable.prototype = { addScrollableClass: function () { if (!this.$element.hasClass("scrollable")) { this.scrollableAdded = true; this.$element.addClass("scrollable"); } }, removeScrollableClass: function () { if (this.scrollableAdded) this.$element.removeClass("scrollable"); }, addSkinClass: function () { if (typeof(this.options.skin) == "string" && !this.$element.hasClass(this.options.skin)) { this.skinClassAdded = true; this.$element.addClass(this.options.skin); } }, removeSkinClass: function () { if (this.skinClassAdded) this.$element.removeClass(this.options.skin); }, addScrollBarComponents: function () { this.assignViewPort(); if (this.$viewPort.length == 0) { this.$element.wrapInner("
"); this.assignViewPort(); this.viewPortAdded = true; } this.assignOverview(); if (this.$overview.length == 0) { this.$viewPort.wrapInner("
"); this.assignOverview(); this.overviewAdded = true; } this.addScrollBar("vertical", "prepend"); this.addScrollBar("horizontal", "append"); }, removeScrollbarComponents: function () { this.removeScrollbar("vertical"); this.removeScrollbar("horizontal"); if (this.overviewAdded) this.$element.unwrap(); if (this.viewPortAdded) this.$element.unwrap(); }, removeScrollbar: function (orientation) { if (this[orientation + "ScrollbarAdded"]) this.$element.find(".scroll-bar." + orientation).remove(); }, assignViewPort: function () { this.$viewPort = this.$element.find(".viewport"); }, assignOverview: function () { this.$overview = this.$viewPort.find(".overview"); }, addScrollBar: function (orientation, fun) { if (this.$element.find(".scroll-bar." + orientation).length == 0) { this.$element[fun]("
") this[orientation + "ScrollbarAdded"] = true; } }, resize: function (keepPosition) { if (this.vScrollbar) this.vScrollbar.resize(keepPosition); if (this.hScrollbar) this.hScrollbar.resize(keepPosition); }, scrollTo: function (element) { if (this.vScrollbar) this.vScrollbar.scrollToElement(element); if (this.hScrollbar) this.hScrollbar.scrollToElement(element); }, scrollToXY: function (x, y) { this.scrollToX(x); this.scrollToY(y); }, scrollToX: function (x) { if (this.hScrollbar) this.hScrollbar.scrollOverviewTo(x, true); }, scrollToY: function (y) { if (this.vScrollbar) this.vScrollbar.scrollOverviewTo(y, true); }, scrollByX: function (x) { if (this.hScrollbar) this.scrollToX(this.hScrollbar.overviewPosition() + x); }, scrollByY: function (y) { if (this.vScrollbar) this.scrollToY(this.vScrollbar.overviewPosition() + y); }, remove: function () { this.removeScrollableClass(); this.removeSkinClass(); this.removeScrollbarComponents(); this.$element.data("scrollable", null); this.removeKeyboardScrolling(); if (this.vScrollbar) this.vScrollbar.remove(); if (this.hScrollbar) this.hScrollbar.remove(); }, setAnimationSpeed: function (speed) { this.options.animationSpeed = speed; }, isInside: function (element, wrappingElement) { var $element = $(element); var $wrappingElement = $(wrappingElement); var elementOffset = $element.offset(); var wrappingElementOffset = $wrappingElement.offset(); return (elementOffset.top >= wrappingElementOffset.top) && (elementOffset.left >= wrappingElementOffset.left) && (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height()) && (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width()) }, initKeyboardScrolling: function () { var _this = this; this.elementKeydown = function (event) { if (document.activeElement === _this.$element[0]) { if (_this.vScrollbar) _this.vScrollbar.keyScroll(event); if (_this.hScrollbar) _this.hScrollbar.keyScroll(event); } } this.$element .attr('tabindex', '-1') .keydown(this.elementKeydown); }, removeKeyboardScrolling: function () { this.$element .removeAttr('tabindex') .off("keydown", this.elementKeydown); }, bindEvents: function () { if (this.options.onCustomScroll) this.$element.on("customScroll", this.options.onCustomScroll); } } var Scrollbar = function (scrollable, sizing) { this.scrollable = scrollable; this.sizing = sizing this.$scrollBar = this.sizing.scrollBar(this.scrollable.$element); this.$thumb = this.$scrollBar.find(".thumb"); this.setScrollPosition(0, 0); this.resize(); this.initMouseMoveScrolling(); this.initMouseWheelScrolling(); this.initTouchScrolling(); this.initMouseClickScrolling(); this.initWindowResize(); } Scrollbar.prototype = { resize: function (keepPosition) { this.overviewSize = this.sizing.size(this.scrollable.$overview); this.calculateViewPortSize(); this.sizing.size(this.scrollable.$viewPort, this.viewPortSize); this.ratio = this.viewPortSize / this.overviewSize; this.sizing.size(this.$scrollBar, this.viewPortSize); this.thumbSize = this.calculateThumbSize(); this.sizing.size(this.$thumb, this.thumbSize); this.maxThumbPosition = this.calculateMaxThumbPosition(); this.maxOverviewPosition = this.calculateMaxOverviewPosition(); this.enabled = (this.overviewSize > this.viewPortSize); if (this.scrollPercent === undefined) this.scrollPercent = 0.0; if (this.enabled) this.rescroll(keepPosition); else this.setScrollPosition(0, 0); this.$scrollBar.toggle(this.enabled); }, calculateViewPortSize: function () { var elementSize = this.sizing.size(this.scrollable.$element); if (elementSize > 0 && !this.maxSizeUsed) { this.viewPortSize = elementSize; this.maxSizeUsed = false; } else { var maxSize = this.sizing.maxSize(this.scrollable.$element); this.viewPortSize = Math.min(maxSize, this.overviewSize); this.maxSizeUsed = true; } }, calculateThumbSize: function () { var fixedSize = this.sizing.fixedThumbSize(this.scrollable.options) var size; if (fixedSize) size = fixedSize; else size = this.ratio * this.viewPortSize return Math.max(size, this.sizing.minSize(this.$thumb)); }, initMouseMoveScrolling: function () { var _this = this; this.$thumb.mousedown(function (event) { if (_this.enabled) _this.startMouseMoveScrolling(event); }); this.documentMouseup = function (event) { _this.stopMouseMoveScrolling(event); }; $(document).mouseup(this.documentMouseup); this.documentMousemove = function (event) { _this.mouseMoveScroll(event); }; $(document).mousemove(this.documentMousemove); this.$thumb.click(function (event) { event.stopPropagation(); }); }, removeMouseMoveScrolling: function () { this.$thumb.off(); $(document).off("mouseup", this.documentMouseup); $(document).off("mousemove", this.documentMousemove); }, initMouseWheelScrolling: function () { var _this = this; this.scrollable.$element.mousewheel(function (event, delta, deltaX, deltaY) { if (_this.enabled) { var scrolled = _this.mouseWheelScroll(deltaX, deltaY); _this.stopEventConditionally(event, scrolled); } }); }, removeMouseWheelScrolling: function () { this.scrollable.$element.off("mousewheel"); }, initTouchScrolling: function () { if (document.addEventListener) { var _this = this; this.elementTouchstart = function (event) { if (_this.enabled) _this.startTouchScrolling(event); } this.scrollable.$element[0].addEventListener("touchstart", this.elementTouchstart); this.documentTouchmove = function (event) { _this.touchScroll(event); } this.scrollable.$element[0].addEventListener("touchmove", this.documentTouchmove); this.elementTouchend = function (event) { _this.stopTouchScrolling(event); } this.scrollable.$element[0].addEventListener("touchend", this.elementTouchend); } }, removeTouchScrolling: function () { if (document.addEventListener) { this.scrollable.$element[0].removeEventListener("touchstart", this.elementTouchstart); document.removeEventListener("touchmove", this.documentTouchmove); this.scrollable.$element[0].removeEventListener("touchend", this.elementTouchend); } }, initMouseClickScrolling: function () { var _this = this; this.scrollBarClick = function (event) { _this.mouseClickScroll(event); }; this.$scrollBar.click(this.scrollBarClick); }, removeMouseClickScrolling: function () { this.$scrollBar.off("click", this.scrollBarClick); }, initWindowResize: function () { if (this.scrollable.options.updateOnWindowResize) { var _this = this; this.windowResize = function () { _this.resize(); }; $(window).resize(this.windowResize); } }, removeWindowResize: function () { $(window).off("resize", this.windowResize); }, isKeyScrolling: function (key) { return this.keyScrollDelta(key) != null; }, keyScrollDelta: function (key) { for (var scrollingKey in this.sizing.scrollingKeys) if (scrollingKey == key) return this.sizing.scrollingKeys[key](this.viewPortSize); return null; }, startMouseMoveScrolling: function (event) { this.mouseMoveScrolling = true; $("body").addClass("not-selectable"); this.setUnselectable($("body"), "on"); this.setScrollEvent(event); event.preventDefault(); }, stopMouseMoveScrolling: function (event) { this.mouseMoveScrolling = false; $("body").removeClass("not-selectable"); this.setUnselectable($("body"), null); }, setUnselectable: function (element, value) { if (element.attr("unselectable") != value) { element.attr("unselectable", value); element.find(':not(input)').attr('unselectable', value); } }, mouseMoveScroll: function (event) { if (this.mouseMoveScrolling) { var delta = this.sizing.mouseDelta(this.scrollEvent, event); this.scrollThumbBy(delta); this.setScrollEvent(event); } }, startTouchScrolling: function (event) { if (event.touches && event.touches.length == 1) { this.setScrollEvent(event.touches[0]); this.touchScrolling = true; event.stopPropagation(); } }, touchScroll: function (event) { if (this.touchScrolling && event.touches && event.touches.length == 1) { var delta = -this.sizing.mouseDelta(this.scrollEvent, event.touches[0]) * this.scrollable.options.swipeSpeed; var scrolled = this.scrollOverviewBy(delta); if (scrolled) this.setScrollEvent(event.touches[0]); this.stopEventConditionally(event, scrolled); } }, stopTouchScrolling: function (event) { this.touchScrolling = false; event.stopPropagation(); }, mouseWheelScroll: function (deltaX, deltaY) { var delta = -this.sizing.wheelDelta(deltaX, deltaY) * this.scrollable.options.wheelSpeed; if (delta != 0) return this.scrollOverviewBy(delta); }, mouseClickScroll: function (event) { var delta = this.viewPortSize - 20; if (event["page" + this.sizing.scrollAxis()] < this.$thumb.offset()[this.sizing.offsetComponent()]) // mouse click over thumb delta = -delta; this.scrollOverviewBy(delta); }, keyScroll: function (event) { var keyDown = event.which; if (this.enabled && this.isKeyScrolling(keyDown)) { var scrolled = this.scrollOverviewBy(this.keyScrollDelta(keyDown)); this.stopEventConditionally(event, scrolled); } }, scrollThumbBy: function (delta) { var thumbPosition = this.thumbPosition(); thumbPosition += delta; thumbPosition = this.positionOrMax(thumbPosition, this.maxThumbPosition); var oldScrollPercent = this.scrollPercent; this.scrollPercent = thumbPosition / this.maxThumbPosition; if (oldScrollPercent != this.scrollPercent) { var overviewPosition = (thumbPosition * this.maxOverviewPosition) / this.maxThumbPosition; this.setScrollPosition(overviewPosition, thumbPosition); this.triggerCustomScroll(oldScrollPercent); return true } else return false; }, thumbPosition: function () { return this.$thumb.position()[this.sizing.offsetComponent()]; }, scrollOverviewBy: function (delta) { var overviewPosition = this.overviewPosition() + delta; return this.scrollOverviewTo(overviewPosition, false); }, overviewPosition: function () { return -this.scrollable.$overview.position()[this.sizing.offsetComponent()]; }, scrollOverviewTo: function (overviewPosition, animate) { overviewPosition = this.positionOrMax(overviewPosition, this.maxOverviewPosition); var oldScrollPercent = this.scrollPercent; this.scrollPercent = overviewPosition / this.maxOverviewPosition; if (oldScrollPercent != this.scrollPercent) { var thumbPosition = this.scrollPercent * this.maxThumbPosition; if (animate) this.setScrollPositionWithAnimation(overviewPosition, thumbPosition); else this.setScrollPosition(overviewPosition, thumbPosition); this.triggerCustomScroll(oldScrollPercent); return true; } else return false; }, positionOrMax: function (p, max) { if (p < 0) return 0; else if (p > max) return max; else return p; }, triggerCustomScroll: function (oldScrollPercent) { this.scrollable.$element.trigger("customScroll", { scrollAxis: this.sizing.scrollAxis(), direction: this.sizing.scrollDirection(oldScrollPercent, this.scrollPercent), scrollPercent: this.scrollPercent * 100 } ); }, rescroll: function (keepPosition) { if (keepPosition) { var overviewPosition = this.positionOrMax(this.overviewPosition(), this.maxOverviewPosition); this.scrollPercent = overviewPosition / this.maxOverviewPosition; var thumbPosition = this.scrollPercent * this.maxThumbPosition; this.setScrollPosition(overviewPosition, thumbPosition); } else { var thumbPosition = this.scrollPercent * this.maxThumbPosition; var overviewPosition = this.scrollPercent * this.maxOverviewPosition; this.setScrollPosition(overviewPosition, thumbPosition); } }, setScrollPosition: function (overviewPosition, thumbPosition) { this.$thumb.css(this.sizing.offsetComponent(), thumbPosition + "px"); this.scrollable.$overview.css(this.sizing.offsetComponent(), -overviewPosition + "px"); }, setScrollPositionWithAnimation: function (overviewPosition, thumbPosition) { var thumbAnimationOpts = {}; var overviewAnimationOpts = {}; thumbAnimationOpts[this.sizing.offsetComponent()] = thumbPosition + "px"; this.$thumb.animate(thumbAnimationOpts, this.scrollable.options.animationSpeed); overviewAnimationOpts[this.sizing.offsetComponent()] = -overviewPosition + "px"; this.scrollable.$overview.animate(overviewAnimationOpts, this.scrollable.options.animationSpeed); }, calculateMaxThumbPosition: function () { return Math.max(0, this.sizing.size(this.$scrollBar) - this.thumbSize); }, calculateMaxOverviewPosition: function () { return Math.max(0, this.sizing.size(this.scrollable.$overview) - this.sizing.size(this.scrollable.$viewPort)); }, setScrollEvent: function (event) { var attr = "page" + this.sizing.scrollAxis(); if (!this.scrollEvent || this.scrollEvent[attr] != event[attr]) this.scrollEvent = {pageX: event.pageX, pageY: event.pageY}; }, scrollToElement: function (element) { var $element = $(element); if (this.sizing.isInside($element, this.scrollable.$overview) && !this.sizing.isInside($element, this.scrollable.$viewPort)) { var elementOffset = $element.offset(); var overviewOffset = this.scrollable.$overview.offset(); var viewPortOffset = this.scrollable.$viewPort.offset(); this.scrollOverviewTo(elementOffset[this.sizing.offsetComponent()] - overviewOffset[this.sizing.offsetComponent()], true); } }, remove: function () { this.removeMouseMoveScrolling(); this.removeMouseWheelScrolling(); this.removeTouchScrolling(); this.removeMouseClickScrolling(); this.removeWindowResize(); }, stopEventConditionally: function (event, condition) { if (condition || this.scrollable.options.preventDefaultScroll) { event.preventDefault(); event.stopPropagation(); } } } var HSizing = function () { } HSizing.prototype = { size: function ($el, arg) { if (arg) return $el.width(arg); else return $el.width(); }, minSize: function ($el) { return parseInt($el.css("min-width")) || 0; }, maxSize: function ($el) { return parseInt($el.css("max-width")) || 0; }, fixedThumbSize: function (options) { return options.fixedThumbWidth; }, scrollBar: function ($el) { return $el.find(".scroll-bar.horizontal"); }, mouseDelta: function (event1, event2) { return event2.pageX - event1.pageX; }, offsetComponent: function () { return "left"; }, wheelDelta: function (deltaX, deltaY) { return deltaX; }, scrollAxis: function () { return "X"; }, scrollDirection: function (oldPercent, newPercent) { return oldPercent < newPercent ? "right" : "left"; }, scrollingKeys: { 37: function (viewPortSize) { return -10; //arrow left }, 39: function (viewPortSize) { return 10; //arrow right } }, isInside: function (element, wrappingElement) { var $element = $(element); var $wrappingElement = $(wrappingElement); var elementOffset = $element.offset(); var wrappingElementOffset = $wrappingElement.offset(); return (elementOffset.left >= wrappingElementOffset.left) && (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width()); } } var VSizing = function () { } VSizing.prototype = { size: function ($el, arg) { if (arg) return $el.height(arg); else return parseInt($el.css("height")) || 0; }, minSize: function ($el) { return parseInt($el.css("min-height")) || 0; }, maxSize: function ($el) { return parseInt($el.css("max-height")) || 0; }, fixedThumbSize: function (options) { return options.fixedThumbHeight; }, scrollBar: function ($el) { return $el.find(".scroll-bar.vertical"); }, mouseDelta: function (event1, event2) { return event2.pageY - event1.pageY; }, offsetComponent: function () { return "top"; }, wheelDelta: function (deltaX, deltaY) { return deltaY; }, scrollAxis: function () { return "Y"; }, scrollDirection: function (oldPercent, newPercent) { return oldPercent < newPercent ? "down" : "up"; }, scrollingKeys: { 38: function (viewPortSize) { return -10; //arrow up }, 40: function (viewPortSize) { return 10; //arrow down }, 33: function (viewPortSize) { return -(viewPortSize - 20); //page up }, 34: function (viewPortSize) { return viewPortSize - 20; //page down } }, isInside: function (element, wrappingElement) { var $element = $(element); var $wrappingElement = $(wrappingElement); var elementOffset = $element.offset(); var wrappingElementOffset = $wrappingElement.offset(); return (elementOffset.top >= wrappingElementOffset.top) && (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height()); } } return this.each(function () { if (options == undefined) options = defaultOptions; if (typeof(options) == "string") { var scrollable = $(this).data("scrollable"); if (scrollable) scrollable[options](args); } else if (typeof(options) == "object") { options = $.extend(defaultOptions, options); new Scrollable($(this), options); } else throw "Invalid type of options"; }); }; })(jQuery); (function ($) { var types = ['DOMMouseScroll', 'mousewheel']; if ($.event.fixHooks) { for (var i = types.length; i;) { $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; } } $.event.special.mousewheel = { setup: function () { if (this.addEventListener) { for (var i = types.length; i;) { this.addEventListener(types[--i], handler, false); } } else { this.onmousewheel = handler; } }, teardown: function () { if (this.removeEventListener) { for (var i = types.length; i;) { this.removeEventListener(types[--i], handler, false); } } else { this.onmousewheel = null; } } }; $.fn.extend({ mousewheel: function (fn) { return fn ? this.on("mousewheel", fn) : this.trigger("mousewheel"); }, unmousewheel: function (fn) { return this.off("mousewheel", fn); } }); function handler(event) { var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; event = $.event.fix(orgEvent); event.type = "mousewheel"; // Old school scrollwheel delta if (orgEvent.wheelDelta) { delta = orgEvent.wheelDelta / 120; } if (orgEvent.detail) { delta = -orgEvent.detail / 3; } // New school multidimensional scroll (touchpads) deltas deltaY = delta; // Gecko if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) { deltaY = 0; deltaX = delta; } // Webkit if (orgEvent.wheelDeltaY !== undefined) { deltaY = orgEvent.wheelDeltaY / 120; } if (orgEvent.wheelDeltaX !== undefined) { deltaX = orgEvent.wheelDeltaX / 120; } // Add event and delta to the front of the arguments args.unshift(event, delta, deltaX, deltaY); return ($.event.dispatch || $.event.handle).apply(this, args); } })(jQuery);