(function ($) { /** * Augment jQuery prototype. */ $.fn.antiscroll = function (options) { return this.each(function () { if ($(this).data('antiscroll')) { $(this).data('antiscroll').destroy(); } $(this).data('antiscroll', new $.Antiscroll(this, options)); }); }; /** * Expose constructor. */ $.Antiscroll = Antiscroll; /** * Antiscroll pane constructor. * * @param {Element|jQuery} main pane * @parma {Object} options * @api public */ function Antiscroll (el, opts) { this.el = $(el); this.options = opts || {}; this.x = false !== this.options.x; this.y = false !== this.options.y; this.padding = undefined == this.options.padding ? 2 : this.options.padding; this.inner = this.el.find('.antiscroll-inner'); this.inner.css({ 'width': '+=' + scrollbarSize() , 'height': '+=' + scrollbarSize() }); this.refresh(); }; /** * refresh scrollbars * * @api public */ Antiscroll.prototype.refresh = function() { var needHScroll = this.inner.get(0).scrollWidth > this.el.width() , needVScroll = this.inner.get(0).scrollHeight > this.el.height(); if (!this.horizontal && needHScroll && this.x) { this.horizontal = new Scrollbar.Horizontal(this); } else if (this.horizontal && !needHScroll) { this.horizontal.destroy(); this.horizontal = null } if (!this.vertical && needVScroll && this.y) { this.vertical = new Scrollbar.Vertical(this); } else if (this.vertical && !needVScroll) { this.vertical.destroy(); this.vertical = null } }; /** * Cleans up. * * @return {Antiscroll} for chaining * @api public */ Antiscroll.prototype.destroy = function () { if (this.horizontal) { this.horizontal.destroy(); } if (this.vertical) { this.vertical.destroy(); } return this; }; /** * Rebuild Antiscroll. * * @return {Antiscroll} for chaining * @api public */ Antiscroll.prototype.rebuild = function () { this.destroy(); this.inner.attr('style', ''); Antiscroll.call(this, this.el, this.options); return this; }; /** * Scrollbar constructor. * * @param {Element|jQuery} element * @api public */ function Scrollbar (pane) { this.pane = pane; this.pane.el.append(this.el); this.innerEl = this.pane.inner.get(0); this.dragging = false; this.enter = false; this.shown = false; // hovering this.pane.el.mouseenter($.proxy(this, 'mouseenter')); this.pane.el.mouseleave($.proxy(this, 'mouseleave')); // dragging this.el.mousedown($.proxy(this, 'mousedown')); // scrolling this.pane.inner.scroll($.proxy(this, 'scroll')); // wheel -optional- this.pane.inner.bind('mousewheel', $.proxy(this, 'mousewheel')); // show var initialDisplay = this.pane.options.initialDisplay; if (initialDisplay !== false) { this.show(); this.hiding = setTimeout($.proxy(this, 'hide'), parseInt(initialDisplay, 10) || 3000); } }; /** * Cleans up. * * @return {Scrollbar} for chaining * @api public */ Scrollbar.prototype.destroy = function () { this.el.remove(); return this; }; /** * Called upon mouseenter. * * @api private */ Scrollbar.prototype.mouseenter = function () { this.enter = true; this.show(); }; /** * Called upon mouseleave. * * @api private */ Scrollbar.prototype.mouseleave = function () { this.enter = false; if (!this.dragging) { this.hide(); } } /** * Called upon wrap scroll. * * @api private */ Scrollbar.prototype.scroll = function () { if (!this.shown) { this.show(); if (!this.enter && !this.dragging) { this.hiding = setTimeout($.proxy(this, 'hide'), 1500); } } this.update(); }; /** * Called upon scrollbar mousedown. * * @api private */ Scrollbar.prototype.mousedown = function (ev) { ev.preventDefault(); this.dragging = true; this.startPageY = ev.pageY - parseInt(this.el.css('top'), 10); this.startPageX = ev.pageX - parseInt(this.el.css('left'), 10); // prevent crazy selections on IE document.onselectstart = function () { return false; }; var pane = this.pane , move = $.proxy(this, 'mousemove') , self = this $(document) .mousemove(move) .mouseup(function () { self.dragging = false; document.onselectstart = null; $(document).unbind('mousemove', move); if (!self.enter) { self.hide(); } }) }; /** * Show scrollbar. * * @api private */ Scrollbar.prototype.show = function (duration) { if (!this.shown) { this.update(); this.el.addClass('antiscroll-scrollbar-shown'); if (this.hiding) { clearTimeout(this.hiding); this.hiding = null; } this.shown = true; } }; /** * Hide scrollbar. * * @api private */ Scrollbar.prototype.hide = function () { var autoHide = this.pane.options.autoHide; if (autoHide !== false && this.shown) { // check for dragging this.el.removeClass('antiscroll-scrollbar-shown'); this.shown = false; } }; /** * Horizontal scrollbar constructor * * @api private */ Scrollbar.Horizontal = function (pane) { this.el = $('
'); Scrollbar.call(this, pane); } /** * Inherits from Scrollbar. */ inherits(Scrollbar.Horizontal, Scrollbar); /** * Updates size/position of scrollbar. * * @api private */ Scrollbar.Horizontal.prototype.update = function () { var paneWidth = this.pane.el.width() , trackWidth = paneWidth - this.pane.padding * 2 , innerEl = this.pane.inner.get(0) this.el .css('width', trackWidth * paneWidth / innerEl.scrollWidth) .css('left', trackWidth * innerEl.scrollLeft / innerEl.scrollWidth) } /** * Called upon drag. * * @api private */ Scrollbar.Horizontal.prototype.mousemove = function (ev) { var trackWidth = this.pane.el.width() - this.pane.padding * 2 , pos = ev.pageX - this.startPageX , barWidth = this.el.width() , innerEl = this.pane.inner.get(0) // minimum top is 0, maximum is the track height var y = Math.min(Math.max(pos, 0), trackWidth - barWidth) innerEl.scrollLeft = (innerEl.scrollWidth - this.pane.el.width()) * y / (trackWidth - barWidth) }; /** * Called upon container mousewheel. * * @api private */ Scrollbar.Horizontal.prototype.mousewheel = function (ev, delta, x, y) { if ((x < 0 && 0 == this.pane.inner.get(0).scrollLeft) || (x > 0 && (this.innerEl.scrollLeft + Math.ceil(this.pane.el.width()) == this.innerEl.scrollWidth))) { ev.preventDefault(); return false; } }; /** * Vertical scrollbar constructor * * @api private */ Scrollbar.Vertical = function (pane) { this.el = $('
'); Scrollbar.call(this, pane); }; /** * Inherits from Scrollbar. */ inherits(Scrollbar.Vertical, Scrollbar); /** * Updates size/position of scrollbar. * * @api private */ Scrollbar.Vertical.prototype.update = function () { var paneHeight = this.pane.el.height() , trackHeight = paneHeight - this.pane.padding * 2 , innerEl = this.innerEl this.el .css('height', trackHeight * paneHeight / innerEl.scrollHeight) .css('top', trackHeight * innerEl.scrollTop / innerEl.scrollHeight) }; /** * Called upon drag. * * @api private */ Scrollbar.Vertical.prototype.mousemove = function (ev) { var paneHeight = this.pane.el.height() , trackHeight = paneHeight - this.pane.padding * 2 , pos = ev.pageY - this.startPageY , barHeight = this.el.height() , innerEl = this.innerEl // minimum top is 0, maximum is the track height var y = Math.min(Math.max(pos, 0), trackHeight - barHeight) innerEl.scrollTop = (innerEl.scrollHeight - paneHeight) * y / (trackHeight - barHeight) }; /** * Called upon container mousewheel. * * @api private */ Scrollbar.Vertical.prototype.mousewheel = function (ev, delta, x, y) { if ((y > 0 && 0 == this.innerEl.scrollTop) || (y < 0 && (this.innerEl.scrollTop + Math.ceil(this.pane.el.height()) == this.innerEl.scrollHeight))) { ev.preventDefault(); return false; } }; /** * Cross-browser inheritance. * * @param {Function} constructor * @param {Function} constructor we inherit from * @api private */ function inherits (ctorA, ctorB) { function f() {}; f.prototype = ctorB.prototype; ctorA.prototype = new f; }; /** * Scrollbar size detection. */ var size; function scrollbarSize () { if (size === undefined) { var div = $( '
' + '
' ); $('body').append(div); var w1 = $('div', div).innerWidth(); div.css('overflow-y', 'scroll'); var w2 = $('div', div).innerWidth(); $(div).remove(); size = w1 - w2; } return size; }; })(jQuery);