Initial commit
[2ch-be.git] / js / jquery.custom-scrollbar.js~
blob1ce91d18c19b77bc40396e4dae52050affbfe8c0
1 (function ($) {
3   $.fn.customScrollbar = function (options, args) {
5     var defaultOptions = {
6       skin: undefined,
7       hScroll: true,
8       vScroll: true,
9       updateOnWindowResize: false,
10       animationSpeed: 300,
11       onCustomScroll: undefined,
12       swipeSpeed: 1,
13       wheelSpeed: 40,
14       fixedThumbWidth: undefined,
15       fixedThumbHeight: undefined
16     }
18     var Scrollable = function (element, options) {
19       this.$element = $(element);
20       this.options = options;
21       this.addScrollableClass();
22       this.addSkinClass();
23       this.addScrollBarComponents();
24       if (this.options.vScroll)
25         this.vScrollbar = new Scrollbar(this, new VSizing());
26       if (this.options.hScroll)
27         this.hScrollbar = new Scrollbar(this, new HSizing());
28       this.$element.data("scrollable", this);
29       this.initKeyboardScrolling();
30       this.bindEvents();
31     }
33     Scrollable.prototype = {
35       addScrollableClass: function () {
36         if (!this.$element.hasClass("scrollable")) {
37           this.scrollableAdded = true;
38           this.$element.addClass("scrollable");
39         }
40       },
42       removeScrollableClass: function () {
43         if (this.scrollableAdded)
44           this.$element.removeClass("scrollable");
45       },
47       addSkinClass: function () {
48         if (typeof(this.options.skin) == "string" && !this.$element.hasClass(this.options.skin)) {
49           this.skinClassAdded = true;
50           this.$element.addClass(this.options.skin);
51         }
52       },
54       removeSkinClass: function () {
55         if (this.skinClassAdded)
56           this.$element.removeClass(this.options.skin);
57       },
59       addScrollBarComponents: function () {
60         this.assignViewPort();
61         if (this.$viewPort.length == 0) {
62           this.$element.wrapInner("<div class=\"viewport\" />");
63           this.assignViewPort();
64           this.viewPortAdded = true;
65         }
66         this.assignOverview();
67         if (this.$overview.length == 0) {
68           this.$viewPort.wrapInner("<div class=\"overview\" />");
69           this.assignOverview();
70           this.overviewAdded = true;
71         }
72         this.addScrollBar("vertical", "prepend");
73         this.addScrollBar("horizontal", "append");
74       },
76       removeScrollbarComponents: function () {
77         this.removeScrollbar("vertical");
78         this.removeScrollbar("horizontal");
79         if (this.overviewAdded)
80           this.$element.unwrap();
81         if (this.viewPortAdded)
82           this.$element.unwrap();
83       },
85       removeScrollbar: function (orientation) {
86         if (this[orientation + "ScrollbarAdded"])
87           this.$element.find(".scroll-bar." + orientation).remove();
88       },
90       assignViewPort: function () {
91         this.$viewPort = this.$element.find(".viewport");
92       },
94       assignOverview: function () {
95         this.$overview = this.$viewPort.find(".overview");
96       },
98       addScrollBar: function (orientation, fun) {
99         if (this.$element.find(".scroll-bar." + orientation).length == 0) {
100           this.$element[fun]("<div class='scroll-bar " + orientation + "'><div class='thumb'></div></div>")
101           this[orientation + "ScrollbarAdded"] = true;
102         }
103       },
105       resize: function (keepPosition) {
106         if (this.vScrollbar)
107           this.vScrollbar.resize(keepPosition);
108         if (this.hScrollbar)
109           this.hScrollbar.resize(keepPosition);
110       },
112       scrollTo: function (element) {
113         if (this.vScrollbar)
114           this.vScrollbar.scrollToElement(element);
115         if (this.hScrollbar)
116           this.hScrollbar.scrollToElement(element);
117       },
119       scrollToXY: function (x, y) {
120         this.scrollToX(x);
121         this.scrollToY(y);
122       },
124       scrollToX: function (x) {
125         if (this.hScrollbar)
126           this.hScrollbar.scrollOverviewTo(x, true);
127       },
129       scrollToY: function (y) {
130         if (this.vScrollbar)
131           this.vScrollbar.scrollOverviewTo(y, true);
132       },
134       remove: function () {
135         this.removeScrollableClass();
136         this.removeSkinClass();
137         this.removeScrollbarComponents();
138         this.$element.data("scrollable", null);
139         this.removeKeyboardScrolling();
140         if (this.vScrollbar)
141           this.vScrollbar.remove();
142         if (this.hScrollbar)
143           this.hScrollbar.remove();
144       },
146       setAnimationSpeed: function (speed) {
147         this.options.animationSpeed = speed;
148       },
150       isInside: function (element, wrappingElement) {
151         var $element = $(element);
152         var $wrappingElement = $(wrappingElement);
153         var elementOffset = $element.offset();
154         var wrappingElementOffset = $wrappingElement.offset();
155         return (elementOffset.top >= wrappingElementOffset.top) && (elementOffset.left >= wrappingElementOffset.left) &&
156           (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height()) &&
157           (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width())
158       },
160       initKeyboardScrolling: function () {
161         var _this = this;
163         this.elementKeydown = function (event) {
164           if (document.activeElement === _this.$element[0]) {
165             if (_this.vScrollbar)
166               _this.vScrollbar.keyScroll(event);
167             if (_this.hScrollbar)
168               _this.hScrollbar.keyScroll(event);
169           }
170         }
172         this.$element
173           .attr('tabindex', '-1')
174           .keydown(this.elementKeydown);
175       },
177       removeKeyboardScrolling: function () {
178         this.$element
179           .removeAttr('tabindex')
180           .unbind("keydown", this.elementKeydown);
181       },
183       bindEvents: function () {
184         if (this.options.onCustomScroll)
185           this.$element.on("customScroll", this.options.onCustomScroll);
186       }
188     }
190     var Scrollbar = function (scrollable, sizing) {
191       this.scrollable = scrollable;
192       this.sizing = sizing
193       this.$scrollBar = this.sizing.scrollBar(this.scrollable.$element);
194       this.$thumb = this.$scrollBar.find(".thumb");
195       this.setScrollPosition(0, 0);
196       this.resize();
197       this.initMouseMoveScrolling();
198       this.initMouseWheelScrolling();
199       this.initTouchScrolling();
200       this.initMouseClickScrolling();
201       this.initWindowResize();
202     }
204     Scrollbar.prototype = {
206       resize: function (keepPosition) {
207         this.scrollable.$viewPort.height(this.scrollable.$element.height());
208         this.sizing.size(this.scrollable.$viewPort, this.sizing.size(this.scrollable.$element));
209         this.viewPortSize = this.sizing.size(this.scrollable.$viewPort);
210         this.overviewSize = this.sizing.size(this.scrollable.$overview);
211         this.ratio = this.viewPortSize / this.overviewSize;
212         this.sizing.size(this.$scrollBar, this.viewPortSize);
213         this.thumbSize = this.calculateThumbSize();
214         this.sizing.size(this.$thumb, this.thumbSize);
215         this.maxThumbPosition = this.calculateMaxThumbPosition();
216         this.maxOverviewPosition = this.calculateMaxOverviewPosition();
217         this.enabled = (this.overviewSize > this.viewPortSize);
218         if (this.scrollPercent === undefined)
219           this.scrollPercent = 0.0;
220         if (this.enabled)
221           this.rescroll(keepPosition);
222         else
223           this.setScrollPosition(0, 0);
224         this.$scrollBar.toggle(this.enabled);
225       },
227       calculateThumbSize: function () {
228         var fixedSize = this.sizing.fixedThumbSize(this.scrollable.options)
229         var size;
230         if (fixedSize)
231           size = fixedSize;
232         else
233           size = this.ratio * this.viewPortSize
234         return Math.max(size, this.sizing.minSize(this.$thumb));
235       },
237       initMouseMoveScrolling: function () {
238         var _this = this;
239         this.$thumb.mousedown(function (event) {
240           if (_this.enabled)
241             _this.startMouseMoveScrolling(event);
242         });
243         this.documentMouseup = function (event) {
244           _this.stopMouseMoveScrolling(event);
245         };
246         $(document).mouseup(this.documentMouseup);
247         this.documentMousemove = function (event) {
248           _this.mouseMoveScroll(event);
249         };
250         $(document).mousemove(this.documentMousemove);
251         this.$thumb.click(function (event) {
252           event.stopPropagation();
253         });
254       },
256       removeMouseMoveScrolling: function () {
257         this.$thumb.unbind();
258         $(document).unbind("mouseup", this.documentMouseup);
259         $(document).unbind("mousemove", this.documentMousemove);
260       },
262       initMouseWheelScrolling: function () {
263         var _this = this;
264         this.scrollable.$element.mousewheel(function (event, delta, deltaX, deltaY) {
265           if (_this.enabled) {
266             if (_this.mouseWheelScroll(deltaX, deltaY)) {
267               event.stopPropagation();
268               event.preventDefault();
269             }
270           }
271         });
272       },
274       removeMouseWheelScrolling: function () {
275         this.scrollable.$element.unbind("mousewheel");
276       },
278       initTouchScrolling: function () {
279         if (document.addEventListener) {
280           var _this = this;
281           this.elementTouchstart = function (event) {
282             if (_this.enabled)
283               _this.startTouchScrolling(event);
284           }
285           this.scrollable.$element[0].addEventListener("touchstart", this.elementTouchstart);
286           this.documentTouchmove = function (event) {
287             _this.touchScroll(event);
288           }
289           document.addEventListener("touchmove", this.documentTouchmove);
290           this.elementTouchend = function (event) {
291             _this.stopTouchScrolling(event);
292           }
293           this.scrollable.$element[0].addEventListener("touchend", this.elementTouchend);
294         }
295       },
297       removeTouchScrolling: function () {
298         if (document.addEventListener) {
299           this.scrollable.$element[0].removeEventListener("touchstart", this.elementTouchstart);
300           document.removeEventListener("touchmove", this.documentTouchmove);
301           this.scrollable.$element[0].removeEventListener("touchend", this.elementTouchend);
302         }
303       },
305       initMouseClickScrolling: function () {
306         var _this = this;
307         this.scrollBarClick = function (event) {
308           _this.mouseClickScroll(event);
309         };
310         this.$scrollBar.click(this.scrollBarClick);
311       },
313       removeMouseClickScrolling: function () {
314         this.$scrollBar.unbind("click", this.scrollBarClick);
315       },
317       initWindowResize: function () {
318         if (this.scrollable.options.updateOnWindowResize) {
319           var _this = this;
320           this.windowResize = function () {
321             _this.resize();
322           };
323           $(window).resize(this.windowResize);
324         }
325       },
327       removeWindowResize: function () {
328         $(window).unbind("resize", this.windowResize);
329       },
331       isKeyScrolling: function (key) {
332         return this.keyScrollDelta(key) != null;
333       },
335       keyScrollDelta: function (key) {
336         for (var scrollingKey in this.sizing.scrollingKeys)
337           if (scrollingKey == key)
338             return this.sizing.scrollingKeys[key](this.viewPortSize);
339         return null;
340       },
342       startMouseMoveScrolling: function (event) {
343         this.mouseMoveScrolling = true;
344         $("html").addClass("not-selectable");
345         this.setUnselectable($("html"), "on");
346         this.setScrollEvent(event);
347       },
349       stopMouseMoveScrolling: function (event) {
350         this.mouseMoveScrolling = false;
351         $("html").removeClass("not-selectable");
352         this.setUnselectable($("html"), null);
353       },
355       setUnselectable: function (element, value) {
356         if (element.attr("unselectable") != value) {
357           element.attr("unselectable", value);
358           element.find(':not(input)').attr('unselectable', value);
359         }
360       },
362       mouseMoveScroll: function (event) {
363         if (this.mouseMoveScrolling) {
364           var delta = this.sizing.mouseDelta(this.scrollEvent, event);
365           this.scrollThumbBy(delta);
366           this.setScrollEvent(event);
367         }
368       },
370       startTouchScrolling: function (event) {
371         if (event.touches && event.touches.length == 1) {
372           this.setScrollEvent(event.touches[0]);
373           this.touchScrolling = true;
374           event.stopPropagation();
375         }
376       },
378       touchScroll: function (event) {
379         if (this.touchScrolling && event.touches && event.touches.length == 1) {
380           var delta = -this.sizing.mouseDelta(this.scrollEvent, event.touches[0]) * this.scrollable.options.swipeSpeed;
381           var scrolled = this.scrollOverviewBy(delta);
382           if (scrolled) {
383             event.stopPropagation();
384             event.preventDefault();
385             this.setScrollEvent(event.touches[0]);
386           }
387         }
388       },
390       stopTouchScrolling: function (event) {
391         this.touchScrolling = false;
392         event.stopPropagation();
393       },
395       mouseWheelScroll: function (deltaX, deltaY) {
396         var delta = -this.sizing.wheelDelta(deltaX, deltaY) * this.scrollable.options.wheelSpeed;
397         if (delta != 0)
398           return this.scrollOverviewBy(delta);
399       },
401       mouseClickScroll: function (event) {
402         var delta = this.viewPortSize - 20;
403         if (event["page" + this.sizing.scrollAxis()] < this.$thumb.offset()[this.sizing.offsetComponent()])
404         // mouse click over thumb
405           delta = -delta;
406         this.scrollOverviewBy(delta);
407       },
409       keyScroll: function (event) {
410         var keyDown = event.which;
411         if (this.enabled && this.isKeyScrolling(keyDown)) {
412           if (this.scrollOverviewBy(this.keyScrollDelta(keyDown)))
413             event.preventDefault();
414         }
415       },
417       scrollThumbBy: function (delta) {
418         var thumbPosition = this.thumbPosition();
419         thumbPosition += delta;
420         thumbPosition = this.positionOrMax(thumbPosition, this.maxThumbPosition);
421         var oldScrollPercent = this.scrollPercent;
422         this.scrollPercent = thumbPosition / this.maxThumbPosition;
423         var overviewPosition = (thumbPosition * this.maxOverviewPosition) / this.maxThumbPosition;
424         this.setScrollPosition(overviewPosition, thumbPosition);
425         if (oldScrollPercent != this.scrollPercent) {
426           this.triggerCustomScroll(oldScrollPercent);
427           return true
428         }
429         else
430           return false;
431       },
433       thumbPosition: function () {
434         return this.$thumb.position()[this.sizing.offsetComponent()];
435       },
437       scrollOverviewBy: function (delta) {
438         var overviewPosition = this.overviewPosition() + delta;
439         return this.scrollOverviewTo(overviewPosition, false);
440       },
442       overviewPosition: function () {
443         return -this.scrollable.$overview.position()[this.sizing.offsetComponent()];
444       },
446       scrollOverviewTo: function (overviewPosition, animate) {
447         overviewPosition = this.positionOrMax(overviewPosition, this.maxOverviewPosition);
448         var oldScrollPercent = this.scrollPercent;
449         this.scrollPercent = overviewPosition / this.maxOverviewPosition;
450         var thumbPosition = this.scrollPercent * this.maxThumbPosition;
451         if (animate)
452           this.setScrollPositionWithAnimation(overviewPosition, thumbPosition);
453         else
454           this.setScrollPosition(overviewPosition, thumbPosition);
455         if (oldScrollPercent != this.scrollPercent) {
456           this.triggerCustomScroll(oldScrollPercent);
457           return true;
458         }
459         else
460           return false;
461       },
463       positionOrMax: function (p, max) {
464         if (p < 0)
465           return 0;
466         else if (p > max)
467           return max;
468         else
469           return p;
470       },
472       triggerCustomScroll: function (oldScrollPercent) {
473         this.scrollable.$element.trigger("customScroll", {
474             scrollAxis: this.sizing.scrollAxis(),
475             direction: this.sizing.scrollDirection(oldScrollPercent, this.scrollPercent),
476             scrollPercent: this.scrollPercent * 100
477           }
478         );
479       },
481       rescroll: function (keepPosition) {
482         if (keepPosition) {
483           var overviewPosition = this.positionOrMax(this.overviewPosition(), this.maxOverviewPosition);
484           this.scrollPercent = overviewPosition / this.maxOverviewPosition;
485           var thumbPosition = this.scrollPercent * this.maxThumbPosition;
486           this.setScrollPosition(overviewPosition, thumbPosition);
487         }
488         else {
489           var thumbPosition = this.scrollPercent * this.maxThumbPosition;
490           var overviewPosition = this.scrollPercent * this.maxOverviewPosition;
491           this.setScrollPosition(overviewPosition, thumbPosition);
492         }
493       },
495       setScrollPosition: function (overviewPosition, thumbPosition) {
496         this.$thumb.css(this.sizing.offsetComponent(), thumbPosition + "px");
497         this.scrollable.$overview.css(this.sizing.offsetComponent(), -overviewPosition + "px");
498       },
500       setScrollPositionWithAnimation: function (overviewPosition, thumbPosition) {
501         var thumbAnimationOpts = {};
502         var overviewAnimationOpts = {};
503         thumbAnimationOpts[this.sizing.offsetComponent()] = thumbPosition + "px";
504         this.$thumb.animate(thumbAnimationOpts, this.scrollable.options.animationSpeed);
505         overviewAnimationOpts[this.sizing.offsetComponent()] = -overviewPosition + "px";
506         this.scrollable.$overview.animate(overviewAnimationOpts, this.scrollable.options.animationSpeed);
507       },
509       calculateMaxThumbPosition: function () {
510         return this.sizing.size(this.$scrollBar) - this.thumbSize;
511       },
513       calculateMaxOverviewPosition: function () {
514         return this.sizing.size(this.scrollable.$overview) - this.sizing.size(this.scrollable.$viewPort);
515       },
517       setScrollEvent: function (event) {
518         var attr = "page" + this.sizing.scrollAxis();
519         if (!this.scrollEvent || this.scrollEvent[attr] != event[attr])
520           this.scrollEvent = {pageX: event.pageX, pageY: event.pageY};
521       },
523       scrollToElement: function (element) {
524         var $element = $(element);
525         if (this.sizing.isInside($element, this.scrollable.$overview) && !this.sizing.isInside($element, this.scrollable.$viewPort)) {
526           var elementOffset = $element.offset();
527           var overviewOffset = this.scrollable.$overview.offset();
528           var viewPortOffset = this.scrollable.$viewPort.offset();
529           this.scrollOverviewTo(elementOffset[this.sizing.offsetComponent()] - overviewOffset[this.sizing.offsetComponent()], true);
530         }
531       },
533       remove: function () {
534         this.removeMouseMoveScrolling();
535         this.removeMouseWheelScrolling();
536         this.removeTouchScrolling();
537         this.removeMouseClickScrolling();
538         this.removeWindowResize();
539       }
541     }
543     var HSizing = function () {
544     }
546     HSizing.prototype = {
547       size: function ($el, arg) {
548         if (arg)
549           return $el.width(arg);
550         else
551           return $el.width();
552       },
554       minSize: function ($el) {
555         return parseInt($el.css("min-width")) || 0;
556       },
558       fixedThumbSize: function (options) {
559         return options.fixedThumbWidth;
560       },
562       scrollBar: function ($el) {
563         return $el.find(".scroll-bar.horizontal");
564       },
566       mouseDelta: function (event1, event2) {
567         return event2.pageX - event1.pageX;
568       },
570       offsetComponent: function () {
571         return "left";
572       },
574       wheelDelta: function (deltaX, deltaY) {
575         return deltaX;
576       },
578       scrollAxis: function () {
579         return "X";
580       },
582       scrollDirection: function (oldPercent, newPercent) {
583         return oldPercent < newPercent ? "right" : "left";
584       },
586       scrollingKeys: {
587         37: function (viewPortSize) {
588           return -10; //arrow left
589         },
590         39: function (viewPortSize) {
591           return 10; //arrow right
592         }
593       },
595       isInside: function (element, wrappingElement) {
596         var $element = $(element);
597         var $wrappingElement = $(wrappingElement);
598         var elementOffset = $element.offset();
599         var wrappingElementOffset = $wrappingElement.offset();
600         return (elementOffset.left >= wrappingElementOffset.left) &&
601           (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width());
602       }
604     }
606     var VSizing = function () {
607     }
609     VSizing.prototype = {
611       size: function ($el, arg) {
612         if (arg)
613           return $el.height(arg);
614         else
615           return $el.height();
616       },
618       minSize: function ($el) {
619         return parseInt($el.css("min-height")) || 0;
620       },
622       fixedThumbSize: function (options) {
623         return options.fixedThumbHeight;
624       },
626       scrollBar: function ($el) {
627         return $el.find(".scroll-bar.vertical");
628       },
630       mouseDelta: function (event1, event2) {
631         return event2.pageY - event1.pageY;
632       },
634       offsetComponent: function () {
635         return "top";
636       },
638       wheelDelta: function (deltaX, deltaY) {
639         return deltaY;
640       },
642       scrollAxis: function () {
643         return "Y";
644       },
646       scrollDirection: function (oldPercent, newPercent) {
647         return oldPercent < newPercent ? "down" : "up";
648       },
650       scrollingKeys: {
651         38: function (viewPortSize) {
652           return -10; //arrow up
653         },
654         40: function (viewPortSize) {
655           return 10; //arrow down
656         },
657         33: function (viewPortSize) {
658           return -(viewPortSize - 20); //page up
659         },
660         34: function (viewPortSize) {
661           return viewPortSize - 20; //page down
662         }
663       },
665       isInside: function (element, wrappingElement) {
666         var $element = $(element);
667         var $wrappingElement = $(wrappingElement);
668         var elementOffset = $element.offset();
669         var wrappingElementOffset = $wrappingElement.offset();
670         return (elementOffset.top >= wrappingElementOffset.top) &&
671           (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height());
672       }
674     }
676     return this.each(function () {
677       if (options == undefined)
678         options = defaultOptions;
679       if (typeof(options) == "string") {
680         var scrollable = $(this).data("scrollable");
681         if (scrollable)
682           scrollable[options](args);
683       }
684       else if (typeof(options) == "object") {
685         options = $.extend(defaultOptions, options);
686         new Scrollable($(this), options);
687       }
688       else
689         throw "Invalid type of options";
690     });
692   }
693   ;
696   (jQuery);
698 (function ($) {
700   var types = ['DOMMouseScroll', 'mousewheel'];
702   if ($.event.fixHooks) {
703     for (var i = types.length; i;) {
704       $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
705     }
706   }
708   $.event.special.mousewheel = {
709     setup: function () {
710       if (this.addEventListener) {
711         for (var i = types.length; i;) {
712           this.addEventListener(types[--i], handler, false);
713         }
714       } else {
715         this.onmousewheel = handler;
716       }
717     },
719     teardown: function () {
720       if (this.removeEventListener) {
721         for (var i = types.length; i;) {
722           this.removeEventListener(types[--i], handler, false);
723         }
724       } else {
725         this.onmousewheel = null;
726       }
727     }
728   };
730   $.fn.extend({
731     mousewheel: function (fn) {
732       return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
733     },
735     unmousewheel: function (fn) {
736       return this.unbind("mousewheel", fn);
737     }
738   });
741   function handler(event) {
742     var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
743     event = $.event.fix(orgEvent);
744     event.type = "mousewheel";
746     // Old school scrollwheel delta
747     if (orgEvent.wheelDelta) {
748       delta = orgEvent.wheelDelta / 120;
749     }
750     if (orgEvent.detail) {
751       delta = -orgEvent.detail / 3;
752     }
754     // New school multidimensional scroll (touchpads) deltas
755     deltaY = delta;
757     // Gecko
758     if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
759       deltaY = 0;
760       deltaX = delta;
761     }
763     // Webkit
764     if (orgEvent.wheelDeltaY !== undefined) {
765       deltaY = orgEvent.wheelDeltaY / 120;
766     }
767     if (orgEvent.wheelDeltaX !== undefined) {
768       deltaX = orgEvent.wheelDeltaX / 120;
769     }
771     // Add event and delta to the front of the arguments
772     args.unshift(event, delta, deltaX, deltaY);
774     return ($.event.dispatch || $.event.handle).apply(this, args);
775   }
777 })(jQuery);