/** Ace custom scroller. It is not as feature-rich as plugins such as NiceScroll but it's good enough for most cases. */ (function($ , undefined) { var Ace_Scroll = function(element , _settings) { var self = this; var settings = $.extend({}, $.fn.ace_scroll.defaults, _settings); this.size = 0; this.$element = $(element); this.element = element; var vertical = true; var disabled = false; var active = false; var created = false; var $content_wrap = null, content_wrap = null; var $track = null, $bar = null, track = null, bar = null; var bar_style = null; var bar_size = 0, bar_pos = 0, bar_max_pos = 0, bar_size_2 = 0, move_bar = true; var reset_once = false; var css_pos, css_size, max_css_size, client_size, scroll_direction, scroll_size; var ratio = 1; var inline_style = false; var mouse_track = false; var mouse_release_target = 'onmouseup' in window ? window : 'html'; var dragEvent = settings.dragEvent || false; var trigger_scroll = _settings.scrollEvent || false; this.create = function(_settings) { if(created) return; //if(disabled) return; if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings); this.size = parseInt(this.$element.attr('data-size')) || settings.size || 200; vertical = !settings['horizontal']; css_pos = vertical ? 'top' : 'left';//'left' for horizontal css_size = vertical ? 'height' : 'width';//'width' for horizontal max_css_size = vertical ? 'maxHeight' : 'maxWidth'; client_size = vertical ? 'clientHeight' : 'clientWidth'; scroll_direction = vertical ? 'scrollTop' : 'scrollLeft'; scroll_size = vertical ? 'scrollHeight' : 'scrollWidth'; this.$element.addClass('ace-scroll '+((vertical? '' : ' scroll-hz') + (settings.styleClass ? ' '+settings.styleClass : ''))); if(this.$element.css('position') == 'static') { inline_style = this.element.style.position; this.element.style.position = 'relative'; } else inline_style = false; this.$element.wrapInner('
'); this.$element.prepend('
'); $content_wrap = this.$element.find('.scroll-content').eq(0); if(!vertical) $content_wrap.wrapInner('
'); content_wrap = $content_wrap.get(0); $track = this.$element.find('.scroll-track').eq(0); $bar = $track.find('.scroll-bar').eq(0); track = $track.get(0); bar = $bar.get(0); bar_style = bar.style; $track.hide(); //if(!touchDrag) { $track.on('mousedown', mouse_down_track); $bar.on('mousedown', mouse_down_bar); //} $content_wrap.on('scroll', function() { if(move_bar) { bar_pos = parseInt(Math.round(this[scroll_direction] * ratio)); bar_style[css_pos] = bar_pos + 'px'; } move_bar = false; if(trigger_scroll) this.$element.trigger('scroll', [content_wrap]); }) if(settings.mouseWheel) { var lock = settings.mouseWheelLock; var lock_anyway = !settings.lockAnyway; this.$element.on('mousewheel.ace_scroll DOMMouseScroll.ace_scroll', function(event) { if(disabled) return; if(!active) return lock_anyway; if(mouse_track) { mouse_track = false; $('html').off('.ace_scroll') $(mouse_release_target).off('.ace_scroll'); if(dragEvent) self.$element.trigger('drag.end'); } var delta = event.originalEvent.detail < 0 || event.originalEvent.wheelDelta > 0 ? 1 : -1 var scrollEnd = false//have we reached the end of scrolling? var clientSize = content_wrap[client_size], scrollAmount = content_wrap[scroll_direction]; if( !lock ) { if(delta == -1) scrollEnd = (content_wrap[scroll_size] <= scrollAmount + clientSize); else scrollEnd = (scrollAmount == 0); } self.move_bar(true); var step = parseInt(Math.round(Math.min(Math.max(clientSize / 8 , 54)) , self.size )) + 1; content_wrap[scroll_direction] = scrollAmount - (delta * step); return scrollEnd && lock_anyway; }) } //swipe not available yet var touchDrag = ace.vars['touch'] && 'ace_drag' in $.event.special && settings.touchDrag //&& !settings.touchSwipe; //add drag event for touch devices to scroll if(touchDrag/** || ($.fn.swipe && settings.touchSwipe)*/) { var dir = '', event_name = touchDrag ? 'ace_drag' : 'swipe'; this.$element.on(event_name + '.ace_scroll', function(event) { dir = event.direction; if( (vertical && (dir == 'up' || dir == 'down')) || (!vertical && (dir == 'left' || dir == 'right')) ) { var distance = vertical ? event.dy : event.dx; if(distance != 0) { if(Math.abs(distance) > 20 && touchDrag) distance = distance * 2; self.move_bar(true); content_wrap[scroll_direction] = content_wrap[scroll_direction] + distance; } //if(event.retval) event.retval['cancel'] = true;//prevents document scroll } //else if(event.retval) event.retval['cancel'] = false;//allows document scroll }) } if(settings.hoverReset) { //some mobile browsers don't have mouseenter this.$element.on('mouseenter.ace_scroll touchstart.ace_scroll', function() { self.reset(); }) } if(!vertical) $content_wrap.children(0).css(css_size, this.size);//the extra wrapper $content_wrap.css(max_css_size , this.size); disabled = false; created = true; } this.is_active = function() { return active; } this.is_enabled = function() { return !disabled; } this.move_bar = function($move) { move_bar = $move; } this.reset = function() { if(disabled) return;// this; if(!created) this.create(); var content_size = vertical ? content_wrap[scroll_size] : this.size; if( (vertical && content_size == 0) || (!vertical && this.element.scrollWidth == 0) ) { //elemen is hidden //this.$element.addClass('scroll-hidden'); this.$element.removeClass('scroll-active') return;// this; } var available_space = vertical ? this.size : content_wrap.clientWidth; if(!vertical) $content_wrap.children(0).css(css_size, this.size);//the extra wrapper $content_wrap.css(max_css_size , this.size); if(content_size > available_space) { active = true; $track.css(css_size, available_space).show(); ratio = parseFloat((available_space / content_size).toFixed(5)) bar_size = parseInt(Math.round(available_space * ratio)); bar_size_2 = parseInt(Math.round(bar_size / 2)); bar_max_pos = available_space - bar_size; bar_pos = parseInt(Math.round(content_wrap[scroll_direction] * ratio)); bar_style[css_size] = bar_size + 'px'; bar_style[css_pos] = bar_pos + 'px'; this.$element.addClass('scroll-active') if(!reset_once) { //this.$element.removeClass('scroll-hidden'); if(settings.reset) { //reset scrollbar to zero position at first content_wrap[scroll_direction] = 0; bar_style[css_pos] = 0; } reset_once = true; } } else { active = false; $track.hide(); this.$element.removeClass('scroll-active'); $content_wrap.css(max_css_size , ''); } return;// this; } this.disable = function() { content_wrap[scroll_direction] = 0; bar_style[css_pos] = 0; disabled = true; active = false; $track.hide(); this.$element.removeClass('scroll-active'); $content_wrap.css(max_css_size , ''); return this; } this.enable = function() { disabled = false; this.reset(); return this; } this.destroy = function() { active = false; disabled = false; created = false; this.$element.removeClass('ace-scroll scroll-hz'+(settings.extraClass ? ' '+settings.extraClass : '')); this.$element.off('.ace_scroll') if(!vertical) { //remove the extra wrapping div $content_wrap.find('> div').children().unwrap(); } $content_wrap.children().unwrap(); $content_wrap.remove(); $track.remove(); if(inline_style !== false) this.element.style.position = inline_style; return this; } this.modify = function(_settings) { if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings); this.destroy(); this.create(); this.reset(); return this; } this.update = function(_settings) { //if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings); this.size = _settings.size; return this; } this.update_scroll = function() { move_bar = false; bar_style[css_pos] = bar_pos + 'px'; content_wrap[scroll_direction] = parseInt(Math.round(bar_pos / ratio)); } function mouse_down_track(e) { e.preventDefault(); e.stopPropagation(); var track_offset = $track.offset(); var track_pos = track_offset[css_pos];//top for vertical, left for horizontal var mouse_pos = vertical ? e.pageY : e.pageX; if(mouse_pos > track_pos + bar_pos) { bar_pos = mouse_pos - track_pos - bar_size + bar_size_2; if(bar_pos > bar_max_pos) { bar_pos = bar_max_pos; } } else { bar_pos = mouse_pos - track_pos - bar_size_2; if(bar_pos < 0) bar_pos = 0; } self.update_scroll() } var mouse_pos1 = -1, mouse_pos2 = -1; function mouse_down_bar(e) { e.preventDefault(); e.stopPropagation(); if(vertical) { mouse_pos2 = mouse_pos1 = e.pageY; } else { mouse_pos2 = mouse_pos1 = e.pageX; } mouse_track = true; $('html').off('mousemove.ace_scroll').on('mousemove.ace_scroll', mouse_move_bar) $(mouse_release_target).off('mouseup.ace_scroll').on('mouseup.ace_scroll', mouse_up_bar); $track.addClass('active'); if(dragEvent) self.$element.trigger('drag.start'); } function mouse_move_bar(e) { e.preventDefault(); e.stopPropagation(); if(vertical) { mouse_pos2 = e.pageY; } else { mouse_pos2 = e.pageX; } if(mouse_pos2 - mouse_pos1 + bar_pos > bar_max_pos) { mouse_pos2 = mouse_pos1 + bar_max_pos - bar_pos; } else if(mouse_pos2 - mouse_pos1 + bar_pos < 0) { mouse_pos2 = mouse_pos1 - bar_pos; } bar_pos = bar_pos + (mouse_pos2 - mouse_pos1); mouse_pos1 = mouse_pos2; if(bar_pos < 0) { bar_pos = 0; } else if(bar_pos > bar_max_pos) { bar_pos = bar_max_pos; } self.update_scroll() } function mouse_up_bar(e) { e.preventDefault(); e.stopPropagation(); mouse_track = false; $('html').off('.ace_scroll') $(mouse_release_target).off('.ace_scroll'); $track.removeClass('active'); if(dragEvent) self.$element.trigger('drag.end'); } this.create(); this.reset(); return this; } $.fn.ace_scroll = function (option,value) { var retval; var $set = this.each(function () { var $this = $(this); var data = $this.data('ace_scroll'); var options = typeof option === 'object' && option; if (!data) $this.data('ace_scroll', (data = new Ace_Scroll(this, options))); //else if(typeof options == 'object') data['modify'](options); if (typeof option === 'string') retval = data[option](value); }); return (retval === undefined) ? $set : retval; }; $.fn.ace_scroll.defaults = { 'size' : 200, 'horizontal': false, 'mouseWheel': true, 'mouseWheelLock': false, 'lockAnyway': false, 'styleClass' : false, 'hoverReset': true //reset scrollbar sizes on mouse hover because of possible sizing changes , 'reset': false //true= set scrollTop = 0 , 'dragEvent': false , 'touchDrag': true , 'touchSwipe': false , 'scrollEvent': false //trigger scroll event /** , 'track' : true, 'show' : false, 'dark': false, 'alwaysVisible': false, 'margin': false, 'thin': false, 'position': 'right' */ } /** $(document).on('ace.settings.ace_scroll', function(e, name) { if(name == 'sidebar_collapsed') $('.ace-scroll').scroller('reset'); }); $(window).on('resize.ace_scroll', function() { $('.ace-scroll').scroller('reset'); }); */ })(window.jQuery);