hjg
2024-10-30 8cf23534166c07e711aac2a25911ada317ba01f0
提交 | 用户 | 时间
58d006 1 /**
A 2  <b>Ace custom scroller</b>. It is not as feature-rich as plugins such as NiceScroll but it's good enough for most cases.
3 */
4 (function($ , undefined) {
5     var Ace_Scroll = function(element , _settings) {
6         var self = this;
7         
8         var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_scroll.defaults);
9         var settings = $.extend({}, $.fn.ace_scroll.defaults, _settings, attrib_values);
10     
11         this.size = 0;
12         this.lock = false;
13         this.lock_anyway = false;
14         
15         this.$element = $(element);
16         this.element = element;
17         
18         var vertical = true;
19
20         var disabled = false;
21         var active = false;
22         var created = false;
23
24         
25         var $content_wrap = null, content_wrap = null;
26         var $track = null, $bar = null, track = null, bar = null;
27         var bar_style = null;
28         
29         var bar_size = 0, bar_pos = 0, bar_max_pos = 0, bar_size_2 = 0, move_bar = true;
30         var reset_once = false;
31         
32         var styleClass = '';
33         var trackFlip = false;//vertical on left or horizontal on top
34         var trackSize = 0;
35
36         var css_pos,
37             css_size,
38             max_css_size,
39             client_size,
40             scroll_direction,
41             scroll_size;
42
43         var ratio = 1;
44         var inline_style = false;
45         var mouse_track = false;
46         var mouse_release_target = 'onmouseup' in window ? window : 'html';
47         var dragEvent = settings.dragEvent || false;
48         
49         var trigger_scroll = _settings.scrollEvent || false;
50         
51         
52         var detached = settings.detached || false;//when detached, hideOnIdle as well?
53         var updatePos = settings.updatePos || false;//default is true
54         
55         var hideOnIdle = settings.hideOnIdle || false;
56         var hideDelay = settings.hideDelay || 1500;
57         var insideTrack = false;//used to hide scroll track when mouse is up and outside of track
58         var observeContent = settings.observeContent || false;
59         var prevContentSize = 0;
60         
61         var is_dirty = true;//to prevent consecutive 'reset' calls
62         
63         this.ref = function() {
64             return this;
65         }
66         
67         this.create = function(_settings) {
68             if(created) return;
69
70             if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings);
71
72             this.size = parseInt(this.$element.attr('data-size')) || settings.size || 200;
73             vertical = !settings['horizontal'];
74
75             css_pos = vertical ? 'top' : 'left';//'left' for horizontal
76             css_size = vertical ? 'height' : 'width';//'width' for horizontal
77             max_css_size = vertical ? 'maxHeight' : 'maxWidth';
78
79             client_size = vertical ? 'clientHeight' : 'clientWidth';
80             scroll_direction = vertical ? 'scrollTop' : 'scrollLeft';
81             scroll_size = vertical ? 'scrollHeight' : 'scrollWidth';
82
83
84
85             this.$element.addClass('ace-scroll');
86             if(this.$element.css('position') == 'static') {
87                 inline_style = this.element.style.position;
88                 this.element.style.position = 'relative';
89             } else inline_style = false;
90
91             var scroll_bar = null;
92             if(!detached) {
93                 this.$element.wrapInner('<div class="scroll-content" />');
94                 this.$element.prepend('<div class="scroll-track"><div class="scroll-bar"></div></div>');
95             }
96             else {
97                 scroll_bar = $('<div class="scroll-track scroll-detached"><div class="scroll-bar"></div></div>').appendTo('body');
98             }
99
100
101             $content_wrap = this.$element;
102             if(!detached) $content_wrap = this.$element.find('.scroll-content').eq(0);
103             
104             if(!vertical) $content_wrap.wrapInner('<div />');
105             
106             content_wrap = $content_wrap.get(0);
107             if(detached) {
108                 //set position for detached scrollbar
109                 $track = scroll_bar;
110                 setTrackPos();
111             }
112             else $track = this.$element.find('.scroll-track').eq(0);
113             
114             $bar = $track.find('.scroll-bar').eq(0);
115             track = $track.get(0);
116             bar = $bar.get(0);
117             bar_style = bar.style;
118
119             //add styling classes and horizontalness
120             if(!vertical) $track.addClass('scroll-hz');
121             if(settings.styleClass) {
122                 styleClass = settings.styleClass;
123                 $track.addClass(styleClass);
124                 trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
125             }
126             
127             //calculate size of track!
128             if(trackSize == 0) {
129                 $track.show();
130                 getTrackSize();
131             }
132             
133             $track.hide();
134             
135
136             //if(!touchDrag) {
137             $track.on('mousedown', mouse_down_track);
138             $bar.on('mousedown', mouse_down_bar);
139             //}
140
141             $content_wrap.on('scroll', function() {
142                 if(move_bar) {
143                     bar_pos = parseInt(Math.round(this[scroll_direction] * ratio));
144                     bar_style[css_pos] = bar_pos + 'px';
145                 }
146                 move_bar = false;
147                 if(trigger_scroll) this.$element.trigger('scroll', [content_wrap]);
148             })
149
150
151             if(settings.mouseWheel) {
152                 this.lock = settings.mouseWheelLock;
153                 this.lock_anyway = settings.lockAnyway;
154
155                 //mousewheel library available?
156                 this.$element.on(!!$.event.special.mousewheel ? 'mousewheel.ace_scroll' : 'mousewheel.ace_scroll DOMMouseScroll.ace_scroll', function(event) {
157                     if(disabled) return;
158                     checkContentChanges(true);
159
160                     if(!active) return !self.lock_anyway;
161
162                     if(mouse_track) {
163                         mouse_track = false;
164                         $('html').off('.ace_scroll')
165                         $(mouse_release_target).off('.ace_scroll');
166                         if(dragEvent) self.$element.trigger('drag.end');
167                     }
168                     
169
170                     event.deltaY = event.deltaY || 0;
171                     var delta = (event.deltaY > 0 || event.originalEvent.detail < 0 || event.originalEvent.wheelDelta > 0) ? 1 : -1
172                     var scrollEnd = false//have we reached the end of scrolling?
173                     
174                     var clientSize = content_wrap[client_size], scrollAmount = content_wrap[scroll_direction];
175                     if( !self.lock ) {
176                         if(delta == -1)    scrollEnd = (content_wrap[scroll_size] <= scrollAmount + clientSize);
177                         else scrollEnd = (scrollAmount == 0);
178                     }
179
180                     self.move_bar(true);
181
182                     //var step = parseInt( Math.min(Math.max(parseInt(clientSize / 8) , 80) , self.size) ) + 1;
183                     var step = parseInt(clientSize / 8);
184                     if(step < 80) step = 80;
185                     if(step > self.size) step = self.size;
186                     step += 1;
187                     
188                     content_wrap[scroll_direction] = scrollAmount - (delta * step);
189
190
191                     return scrollEnd && !self.lock_anyway;
192                 })
193             }
194             
195             
196             //swipe not available yet
197             var touchDrag = ace.vars['touch'] && 'ace_drag' in $.event.special && settings.touchDrag //&& !settings.touchSwipe;
198             //add drag event for touch devices to scroll
199             if(touchDrag/** || ($.fn.swipe && settings.touchSwipe)*/) {
200                 var dir = '', event_name = touchDrag ? 'ace_drag' : 'swipe';
201                 this.$element.on(event_name + '.ace_scroll', function(event) {
202                     if(disabled) {
203                         event.retval.cancel = true;
204                         return;
205                     }
206                     checkContentChanges(true);
207                     
208                     if(!active) {
209                         event.retval.cancel = this.lock_anyway;
210                         return;
211                     }
212
213                     dir = event.direction;
214                     if( (vertical && (dir == 'up' || dir == 'down'))
215                         ||
216                         (!vertical && (dir == 'left' || dir == 'right'))
217                        )
218                     {
219                         var distance = vertical ? event.dy : event.dx;
220
221                         if(distance != 0) {
222                             if(Math.abs(distance) > 20 && touchDrag) distance = distance * 2;
223
224                             self.move_bar(true);
225                             content_wrap[scroll_direction] = content_wrap[scroll_direction] + distance;
226                         }
227                     }
228                     
229                 })
230             }
231             
232             
233             /////////////////////////////////
234             
235             if(hideOnIdle) {
236                 $track.addClass('idle-hide');
237             }
238             if(observeContent) {
239                 $track.on('mouseenter.ace_scroll', function() {
240                     insideTrack = true;
241                     checkContentChanges(false);
242                 }).on('mouseleave.ace_scroll', function() {
243                     insideTrack = false;
244                     if(mouse_track == false) hideScrollbars();
245                 });
246             }
247
248
249             
250             //some mobile browsers don't have mouseenter
251             this.$element.on('mouseenter.ace_scroll touchstart.ace_scroll', function(e) {
252                 is_dirty = true;
253                 if(observeContent) checkContentChanges(true);
254                 else if(settings.hoverReset) self.reset(true);
255                 
256                 $track.addClass('scroll-hover');
257             }).on('mouseleave.ace_scroll touchend.ace_scroll', function() {
258                 $track.removeClass('scroll-hover');
259             });
260             //
261
262             if(!vertical) $content_wrap.children(0).css(css_size, this.size);//the extra wrapper
263             $content_wrap.css(max_css_size , this.size);
264             
265             disabled = false;
266             created = true;
267         }
268         this.is_active = function() {
269             return active;
270         }
271         this.is_enabled = function() {
272             return !disabled;
273         }
274         this.move_bar = function($move) {
275             move_bar = $move;
276         }
277         
278         this.get_track = function() {
279             return track;
280         }
281
282         this.reset = function(innert_call) {
283             if(disabled) return;// this;
284             if(!created) this.create();
285             /////////////////////
286             var size = this.size;
287             
288             if(innert_call && !is_dirty) {
289                 return;
290             }
291             is_dirty = false;
292
293             if(detached) {
294                 var border_size = parseInt(Math.round( (parseInt($content_wrap.css('border-top-width')) + parseInt($content_wrap.css('border-bottom-width'))) / 2.5 ));//(2.5 from trial?!)
295                 size -= border_size;//only if detached
296             }
297     
298             var content_size   = vertical ? content_wrap[scroll_size] : size;
299             if( (vertical && content_size == 0) || (!vertical && this.element.scrollWidth == 0) ) {
300                 //element is hidden
301                 //this.$element.addClass('scroll-hidden');
302                 $track.removeClass('scroll-active')
303                 return;// this;
304             }
305
306             var available_space = vertical ? size : content_wrap.clientWidth;
307
308             if(!vertical) $content_wrap.children(0).css(css_size, size);//the extra wrapper
309             $content_wrap.css(max_css_size , this.size);
310             
311
312             if(content_size > available_space) {
313                 active = true;
314                 $track.css(css_size, available_space).show();
315
316                 ratio = parseFloat((available_space / content_size).toFixed(5))
317                 
318                 bar_size = parseInt(Math.round(available_space * ratio));
319                 bar_size_2 = parseInt(Math.round(bar_size / 2));
320
321                 bar_max_pos = available_space - bar_size;
322                 bar_pos = parseInt(Math.round(content_wrap[scroll_direction] * ratio));
323
324                 bar_style[css_size] = bar_size + 'px';
325                 bar_style[css_pos] = bar_pos + 'px';
326                 
327                 $track.addClass('scroll-active');
328                 
329                 if(trackSize == 0) {
330                     getTrackSize();
331                 }
332
333                 if(!reset_once) {
334                     //this.$element.removeClass('scroll-hidden');
335                     if(settings.reset) {
336                         //reset scrollbar to zero position at first                            
337                         content_wrap[scroll_direction] = 0;
338                         bar_style[css_pos] = 0;
339                     }
340                     reset_once = true;
341                 }
342                 
343                 if(detached) setTrackPos();
344             } else {
345                 active = false;
346                 $track.hide();
347                 $track.removeClass('scroll-active');
348                 $content_wrap.css(max_css_size , '');
349             }
350
351             return;// this;
352         }
353         this.disable = function() {
354             content_wrap[scroll_direction] = 0;
355             bar_style[css_pos] = 0;
356
357             disabled = true;
358             active = false;
359             $track.hide();
360             
361             this.$element.addClass('scroll-disabled');
362             
363             $track.removeClass('scroll-active');
364             $content_wrap.css(max_css_size , '');
365         }
366         this.enable = function() {
367             disabled = false;
368             this.$element.removeClass('scroll-disabled');
369         }
370         this.destroy = function() {
371             active = false;
372             disabled = false;
373             created = false;
374             
375             this.$element.removeClass('ace-scroll scroll-disabled scroll-active');
376             this.$element.off('.ace_scroll')
377
378             if(!detached) {
379                 if(!vertical) {
380                     //remove the extra wrapping div
381                     $content_wrap.find('> div').children().unwrap();
382                 }
383                 $content_wrap.children().unwrap();
384                 $content_wrap.remove();
385             }
386             
387             $track.remove();
388             
389             if(inline_style !== false) this.element.style.position = inline_style;
390             
391             if(idleTimer != null) {
392                 clearTimeout(idleTimer);
393                 idleTimer = null;
394             }
395         }
396         this.modify = function(_settings) {
397             if(_settings) settings = $.extend({}, settings, _settings);
398             
399             this.destroy();
400             this.create();
401             is_dirty = true;
402             this.reset(true);
403         }
404         this.update = function(_settings) {
405             if(_settings) settings = $.extend({}, settings, _settings);
406
407             this.size = settings.size || this.size;
408             
409             this.lock = settings.mouseWheelLock || this.lock;
410             this.lock_anyway = settings.lockAnyway || this.lock_anyway;
411             
412             hideOnIdle = settings.hideOnIdle || hideOnIdle;
413             hideDelay = settings.hideDelay || hideDelay;
414             observeContent = settings.observeContent || false;
415             
416             dragEvent = settings.dragEvent || false;
417             
418             if(typeof _settings.styleClass !== 'undefined') {
419                 if(styleClass) $track.removeClass(styleClass);
420                 styleClass = _settings.styleClass;
421                 if(styleClass) $track.addClass(styleClass);
422                 trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
423             }
424         }
425         
426         this.start = function() {
427             content_wrap[scroll_direction] = 0;
428         }
429         this.end = function() {
430             content_wrap[scroll_direction] = content_wrap[scroll_size];
431         }
432         
433         this.hide = function() {
434             $track.hide();
435         }
436         this.show = function() {
437             $track.show();
438         }
439
440         
441         this.update_scroll = function() {
442             move_bar = false;
443             bar_style[css_pos] = bar_pos + 'px';
444             content_wrap[scroll_direction] = parseInt(Math.round(bar_pos / ratio));
445         }
446
447         function mouse_down_track(e) {
448             e.preventDefault();
449             e.stopPropagation();
450                 
451             var track_offset = $track.offset();
452             var track_pos = track_offset[css_pos];//top for vertical, left for horizontal
453             var mouse_pos = vertical ? e.pageY : e.pageX;
454             
455             if(mouse_pos > track_pos + bar_pos) {
456                 bar_pos = mouse_pos - track_pos - bar_size + bar_size_2;
457                 if(bar_pos > bar_max_pos) {                        
458                     bar_pos = bar_max_pos;
459                 }
460             }
461             else {
462                 bar_pos = mouse_pos - track_pos - bar_size_2;
463                 if(bar_pos < 0) bar_pos = 0;
464             }
465
466             self.update_scroll()
467         }
468
469         var mouse_pos1 = -1, mouse_pos2 = -1;
470         function mouse_down_bar(e) {
471             e.preventDefault();
472             e.stopPropagation();
473
474             if(vertical) {
475                 mouse_pos2 = mouse_pos1 = e.pageY;
476             } else {
477                 mouse_pos2 = mouse_pos1 = e.pageX;
478             }
479
480             mouse_track = true;
481             $('html').off('mousemove.ace_scroll').on('mousemove.ace_scroll', mouse_move_bar)
482             $(mouse_release_target).off('mouseup.ace_scroll').on('mouseup.ace_scroll', mouse_up_bar);
483             
484             $track.addClass('active');
485             if(dragEvent) self.$element.trigger('drag.start');
486         }
487         function mouse_move_bar(e) {
488             e.preventDefault();
489             e.stopPropagation();
490
491             if(vertical) {
492                 mouse_pos2 = e.pageY;
493             } else {
494                 mouse_pos2 = e.pageX;
495             }
496             
497
498             if(mouse_pos2 - mouse_pos1 + bar_pos > bar_max_pos) {
499                 mouse_pos2 = mouse_pos1 + bar_max_pos - bar_pos;
500             } else if(mouse_pos2 - mouse_pos1 + bar_pos < 0) {
501                 mouse_pos2 = mouse_pos1 - bar_pos;
502             }
503             bar_pos = bar_pos + (mouse_pos2 - mouse_pos1);
504
505             mouse_pos1 = mouse_pos2;
506
507             if(bar_pos < 0) {
508                 bar_pos = 0;
509             }
510             else if(bar_pos > bar_max_pos) {
511                 bar_pos = bar_max_pos;
512             }
513             
514             self.update_scroll()
515         }
516         function mouse_up_bar(e) {
517             e.preventDefault();
518             e.stopPropagation();
519             
520             mouse_track = false;
521             $('html').off('.ace_scroll')
522             $(mouse_release_target).off('.ace_scroll');
523
524             $track.removeClass('active');
525             if(dragEvent) self.$element.trigger('drag.end');
526             
527             if(active && hideOnIdle && !insideTrack) hideScrollbars();
528         }
529         
530         
531         var idleTimer = null;
532         var prevCheckTime = 0;
533         function checkContentChanges(hideSoon) {
534             //check if content size has been modified since last time?
535             //and with at least 1s delay
536             var newCheck = +new Date();
537             if(observeContent && newCheck - prevCheckTime > 1000) {
538                 var newSize = content_wrap[scroll_size];
539                 if(prevContentSize != newSize) {
540                     prevContentSize = newSize;
541                     is_dirty = true;
542                     self.reset(true);
543                 }
544                 prevCheckTime = newCheck;
545             }
546             
547             //show scrollbars when not idle anymore i.e. triggered by mousewheel, dragging, etc
548             if(active && hideOnIdle) {
549                 if(idleTimer != null) {
550                     clearTimeout(idleTimer);
551                     idleTimer = null;
552                 }
553                 $track.addClass('not-idle');
554             
555                 if(!insideTrack && hideSoon == true) {
556                     //hideSoon is false when mouse enters track
557                     hideScrollbars();
558                 }
559             }
560         }
561
562         function hideScrollbars() {
563             if(idleTimer != null) {
564                 clearTimeout(idleTimer);
565                 idleTimer = null;
566             }
567             idleTimer = setTimeout(function() {
568                 idleTimer = null;
569                 $track.removeClass('not-idle');
570             } , hideDelay);
571         }
572         
573         //for detached scrollbars
574         function getTrackSize() {
575             $track.css('visibility', 'hidden').addClass('scroll-hover');
576             if(vertical) trackSize = parseInt($track.outerWidth()) || 0;
577              else trackSize = parseInt($track.outerHeight()) || 0;
578             $track.css('visibility', '').removeClass('scroll-hover');
579         }
580         this.track_size = function() {
581             if(trackSize == 0) getTrackSize();
582             return trackSize;
583         }
584         
585         //for detached scrollbars
586         function setTrackPos() {
587             if(updatePos === false) return;
588         
589             var off = $content_wrap.offset();//because we want it relative to parent not document
590             var left = off.left;
591             var top = off.top;
592
593             if(vertical) {
594                 if(!trackFlip) {
595                     left += ($content_wrap.outerWidth() - trackSize)
596                 }
597             }
598             else {
599                 if(!trackFlip) {
600                     top += ($content_wrap.outerHeight() - trackSize)
601                 }
602             }
603             
604             if(updatePos === true) $track.css({top: parseInt(top), left: parseInt(left)});
605             else if(updatePos === 'left') $track.css('left', parseInt(left));
606             else if(updatePos === 'top') $track.css('top', parseInt(top));
607         }
608         
609
610
611         this.create();
612         is_dirty = true;
613         this.reset(true);
614         prevContentSize = content_wrap[scroll_size];
615
616         return this;
617     }
618
619     
620     $.fn.ace_scroll = function (option,value) {
621         var retval;
622
623         var $set = this.each(function () {
624             var $this = $(this);
625             var data = $this.data('ace_scroll');
626             var options = typeof option === 'object' && option;
627
628             if (!data) $this.data('ace_scroll', (data = new Ace_Scroll(this, options)));
629              //else if(typeof options == 'object') data['modify'](options);
630             if (typeof option === 'string') retval = data[option](value);
631         });
632
633         return (retval === undefined) ? $set : retval;
634     };
635
636
637     $.fn.ace_scroll.defaults = {
638         'size' : 200,
639         'horizontal': false,
640         'mouseWheel': true,
641         'mouseWheelLock': false,
642         'lockAnyway': false,
643         'styleClass' : false,
644         
645         'observeContent': false,
646         'hideOnIdle': false,
647         'hideDelay': 1500,
648         
649         'hoverReset': true //reset scrollbar sizes on mouse hover because of possible sizing changes
650         ,
651         'reset': false //true= set scrollTop = 0
652         ,
653         'dragEvent': false
654         ,
655         'touchDrag': true
656         ,
657         'touchSwipe': false
658         ,
659         'scrollEvent': false //trigger scroll event
660
661         ,
662         'detached': false
663         ,
664         'updatePos': true
665         /**
666         ,        
667         'track' : true,
668         'show' : false,
669         'dark': false,
670         'alwaysVisible': false,
671         'margin': false,
672         'thin': false,
673         'position': 'right'
674         */
675      }
676
677     /**
678     $(document).on('ace.settings.ace_scroll', function(e, name) {
679         if(name == 'sidebar_collapsed') $('.ace-scroll').scroller('reset');
680     });
681     $(window).on('resize.ace_scroll', function() {
682         $('.ace-scroll').scroller('reset');
683     });
684     */
685
686 })(window.jQuery);