/*! nanoScrollerJS - v0.7 * http://jamesflorentino.github.com/nanoScrollerJS/ * Copyright (c) 2013 James Florentino; Licensed under MIT * * modified by RainLoop Team */ (function($, window, document) { "use strict"; var BROWSER_IS_IE7, BROWSER_SCROLLBAR_WIDTH, DOMSCROLL, DOWN, DRAG, KEYDOWN, KEYUP, MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEWHEEL, NanoScroll, PANEDOWN, RESIZE, SCROLL, SCROLLBAR, TOUCHMOVE, UP, WHEEL, defaults, getBrowserScrollbarWidth; defaults = { /** a classname for the pane element. @property paneClass @type {string} @default 'pane' */ paneClass: 'pane', /** a classname for the slider element. @property sliderClass @type {string} @default 'slider' */ sliderClass: 'slider', /** a classname for the content element. @property contentClass @type {string} @default 'content' */ contentClass: 'content', /** a setting to enable native scrolling in iOS devices. @property iOSNativeScrolling @type {boolean} @default false */ iOSNativeScrolling: false, /** a setting to prevent the rest of the page being scrolled when user scrolls the `.content` element. @property preventPageScrolling @type {boolean} @default false */ preventPageScrolling: false, /** a setting to disable binding to the resize event. @property disableResize @type {boolean} @default false */ disableResize: false, /** a setting to make the scrollbar always visible. @property alwaysVisible @type {boolean} @default false */ alwaysVisible: false, /** a default timeout for the `flash()` method. @property flashDelay @type {number} @default 1500 */ flashDelay: 1500, /** a minimum height for the `.slider` element. @property sliderMinHeight @type {number} @default 20 */ sliderMinHeight: 20, /** a maximum height for the `.slider` element. @property sliderMaxHeight @type {?number} @default null */ sliderMaxHeight: null }; /** @property SCROLLBAR @type {string} @static @final @private */ SCROLLBAR = 'scrollbar'; /** @property SCROLL @type {string} @static @final @private */ SCROLL = 'scroll'; /** @property MOUSEDOWN @type {string} @final @private */ MOUSEDOWN = 'mousedown'; /** @property MOUSEMOVE @type {string} @static @final @private */ MOUSEMOVE = 'mousemove'; /** @property MOUSEWHEEL @type {string} @final @private */ MOUSEWHEEL = 'mousewheel'; /** @property MOUSEUP @type {string} @static @final @private */ MOUSEUP = 'mouseup'; /** @property RESIZE @type {string} @final @private */ RESIZE = 'resize'; /** @property DRAG @type {string} @static @final @private */ DRAG = 'drag'; /** @property UP @type {string} @static @final @private */ UP = 'up'; /** @property PANEDOWN @type {string} @static @final @private */ PANEDOWN = 'panedown'; /** @property DOMSCROLL @type {string} @static @final @private */ DOMSCROLL = 'DOMMouseScroll'; /** @property DOWN @type {string} @static @final @private */ DOWN = 'down'; /** @property WHEEL @type {string} @static @final @private */ WHEEL = 'wheel'; /** @property KEYDOWN @type {string} @static @final @private */ KEYDOWN = 'keydown'; /** @property KEYUP @type {string} @static @final @private */ KEYUP = 'keyup'; /** @property TOUCHMOVE @type {string} @static @final @private */ TOUCHMOVE = 'touchmove'; /** @property BROWSER_IS_IE7 @type {boolean} @static @final @private */ BROWSER_IS_IE7 = window.navigator.appName === 'Microsoft Internet Explorer' && /msie 7./i.test(window.navigator.appVersion) && window.ActiveXObject; /** @property BROWSER_SCROLLBAR_WIDTH @type Number @static @default null @private */ BROWSER_SCROLLBAR_WIDTH = null; /** Returns browser's native scrollbar width @method getBrowserScrollbarWidth @return {number} the scrollbar width in pixels @static @private */ getBrowserScrollbarWidth = function() { var outer, outerStyle, scrollbarWidth; outer = document.createElement('div'); outerStyle = outer.style; outerStyle.position = 'absolute'; outerStyle.width = '100px'; outerStyle.height = '100px'; outerStyle.overflow = SCROLL; outerStyle.top = '-9999px'; outer.className = 'nano-visibility-hidden'; document.body.appendChild(outer); scrollbarWidth = outer.offsetWidth - outer.clientWidth; document.body.removeChild(outer); return scrollbarWidth; }; /** @class NanoScroll @param element {HTMLElement|Node} the main element @param options {Object} nanoScroller's options @constructor */ NanoScroll = (function() { function NanoScroll(el, options) { this.el = el; this.options = options; BROWSER_SCROLLBAR_WIDTH || (BROWSER_SCROLLBAR_WIDTH = getBrowserScrollbarWidth()); this.$el = $(this.el); this.doc = $(document); this.win = $(window); this.$content = this.$el.children("." + options.contentClass); this.$content.attr('tabindex', 0); this.content = this.$content[0]; if (this.options.iOSNativeScrolling && (this.el.style.WebkitOverflowScrolling != null)) { this.nativeScrolling(); } else { this.generate(); } this.createEvents(); this.addEvents(); this.reset(); } /** Prevents the rest of the page being scrolled when user scrolls the `.content` element. @method preventScrolling @param e {Event} @param direction {String} Scroll direction (up or down) @private */ NanoScroll.prototype.preventScrolling = function(e, direction) { if (!this.isActive && !this.isActive2) { return; } if (e.type === DOMSCROLL) { if (direction === DOWN && e.originalEvent.detail > 0 || direction === UP && e.originalEvent.detail < 0) { e.preventDefault(); } } else if (e.type === MOUSEWHEEL) { if (!e.originalEvent || !e.originalEvent.wheelDelta) { return; } if (direction === DOWN && e.originalEvent.wheelDelta < 0 || direction === UP && e.originalEvent.wheelDelta > 0) { e.preventDefault(); } } }; /** Enable iOS native scrolling */ NanoScroll.prototype.scrollClassTimer = 0; NanoScroll.prototype.scrollClassTrigger = function() { window.clearTimeout(this.scrollClassTimer); var _this = this; _this.$el.addClass('nano-scrollevent'); _this.pane.addClass('activescroll'); _this.pane2.addClass('activescroll'); this.scrollClassTimer = window.setTimeout(function () { _this.$el.removeClass('nano-scrollevent'); _this.pane.removeClass('activescroll'); _this.pane2.removeClass('activescroll'); }, 1000); }; NanoScroll.prototype.nativeScrolling = function() { this.$content.css({ WebkitOverflowScrolling: 'touch' }); this.iOSNativeScrolling = true; this.isActive = true; this.isActive2 = true; }; /** Updates those nanoScroller properties that are related to current scrollbar position. @method updateScrollValues @private */ NanoScroll.prototype.updateScrollValues = function() { var content, limit = 8; content = this.content; this.maxScrollTop = content.scrollHeight - content.clientHeight; this.maxScroll2Left = content.scrollWidth - content.clientWidth; this.contentScrollTop = content.scrollTop; this.contentScroll2Left = content.scrollLeft; if (!this.iOSNativeScrolling) { this.maxSliderTop = this.paneHeight - this.sliderHeight; this.maxSlider2Left = this.pane2Width - this.slider2Width; this.sliderTop = this.contentScrollTop * this.maxSliderTop / this.maxScrollTop; this.slider2Left = this.contentScroll2Left * this.maxSlider2Left / this.maxScroll2Left; if (limit < this.sliderTop) { this.$el.addClass('nano-scrolllimit-top'); } else { this.$el.removeClass('nano-scrolllimit-top'); } if (this.contentScrollTop + limit >= this.maxScrollTop) { this.$el.removeClass('nano-scrolllimit-bottom'); } else { this.$el.addClass('nano-scrolllimit-bottom'); } } }; /** Creates event related methods @method createEvents @private */ NanoScroll.prototype.createEvents = function() { var _this = this; this.events = { down: function(e) { _this.isBeingDragged = true; _this.offsetY = e.pageY - _this.slider.offset().top; _this.pane.addClass('active'); _this.doc.bind(MOUSEMOVE, _this.events[DRAG]).bind(MOUSEUP, _this.events[UP]); return false; }, down2: function(e) { _this.isBeingDragged2 = true; _this.offsetX = e.pageX - _this.slider2.offset().left; _this.pane2.addClass('active'); _this.doc.bind(MOUSEMOVE, _this.events['drag2']).bind(MOUSEUP, _this.events['up2']); return false; }, drag: function(e) { _this.sliderY = e.pageY - _this.$el.offset().top - _this.offsetY; _this.scroll(); _this.updateScrollValues(); if (_this.contentScrollTop >= _this.maxScrollTop) { _this.$el.trigger('scrollend'); } else if (_this.contentScrollTop === 0) { _this.$el.trigger('scrolltop'); } return false; }, drag2: function(e) { _this.slider2X = e.pageX - _this.$el.offset().left - _this.offsetX; _this.scroll(); _this.updateScrollValues(); /* if (_this.contentScrollLeft >= _this.maxScrollLeft) { _this.$el.trigger('scrollend'); } else if (_this.contentScrollLeft === 0) { _this.$el.trigger('scrolltop'); }*/ return false; }, up: function() { _this.isBeingDragged = false; _this.pane.removeClass('active'); _this.doc.unbind(MOUSEMOVE, _this.events[DRAG]).unbind(MOUSEUP, _this.events[UP]); return false; }, up2: function() { _this.isBeingDragged2 = false; _this.pane2.removeClass('active'); _this.doc.unbind(MOUSEMOVE, _this.events['drag2']).unbind(MOUSEUP, _this.events['up2']); return false; }, resize: function() { _this.reset(); }, panedown: function(e) { _this.sliderY = (e.offsetY || e.originalEvent.layerY) - (_this.sliderHeight * 0.5); _this.scroll(); _this.events.down(e); return false; }, panedown2: function(e) { _this.slider2X = (e.offsetX || e.originalEvent.layerX) - (_this.slider2Width * 0.5); _this.scroll(); _this.events.down2(e); return false; }, scroll: function(e) { if (_this.isBeingDragged || _this.isBeingDragged2) { return; } _this.updateScrollValues(); if (!_this.iOSNativeScrolling) { _this.sliderY = _this.sliderTop; _this.slider.css({ top: _this.sliderTop }); _this.slider2X = _this.slider2Left; _this.slider2.css({ left: _this.slider2Left }); } if (!e) { return; } if (_this.contentScrollTop >= _this.maxScrollTop) { if (_this.options.preventPageScrolling) { _this.preventScrolling(e, DOWN); } _this.$el.trigger('scrollend'); } else if (_this.contentScrollTop === 0) { if (_this.options.preventPageScrolling) { _this.preventScrolling(e, UP); } _this.$el.trigger('scrolltop'); } if (!_this.iOSNativeScrolling) { _this.scrollClassTrigger(); } }, /** * @param {{wheelDeltaY:number, delta:number}} e */ wheel: function(e) { if (!e || undefined === e.wheelDeltaY || undefined === e.delta) { return; } _this.sliderY += -e.wheelDeltaY || -e.delta; _this.scroll(); return false; } }; }; /** Adds event listeners with jQuery. @method addEvents @private */ NanoScroll.prototype.addEvents = function() { var events; this.removeEvents(); events = this.events; if (!this.options.disableResize) { this.win.bind(RESIZE, events[RESIZE]); } if (!this.iOSNativeScrolling) { this.slider.bind(MOUSEDOWN, events[DOWN]); this.slider2.bind(MOUSEDOWN, events['down2']); this.pane.bind(MOUSEDOWN, events[PANEDOWN]);//.bind("" + MOUSEWHEEL + " " + DOMSCROLL, events[WHEEL]); this.pane2.bind(MOUSEDOWN, events['panedown2']);//.bind("" + MOUSEWHEEL + " " + DOMSCROLL, events[WHEEL]); } this.$content.bind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]); }; /** Removes event listeners with jQuery. @method removeEvents @private */ NanoScroll.prototype.removeEvents = function() { var events; events = this.events; this.win.unbind(RESIZE, events[RESIZE]); if (!this.iOSNativeScrolling) { this.slider.unbind(); this.pane.unbind(); this.slider2.unbind(); this.pane2.unbind(); } this.$content.unbind("" + SCROLL + " " + MOUSEWHEEL + " " + DOMSCROLL + " " + TOUCHMOVE, events[SCROLL]); }; /** Generates nanoScroller's scrollbar and elements for it. @method generate @chainable @private */ NanoScroll.prototype.generate = function() { var contentClass, cssRule, options, paneClass, sliderClass; options = this.options; paneClass = options.paneClass, sliderClass = options.sliderClass, contentClass = options.contentClass; if (!this.$el.find("." + paneClass).length && !this.$el.find("." + sliderClass).length) { this.$el.append("