3 $.fn.customScrollbar = function (options, args) {
9 updateOnWindowResize: false,
11 onCustomScroll: undefined,
14 fixedThumbWidth: undefined,
15 fixedThumbHeight: undefined
18 var Scrollable = function (element, options) {
19 this.$element = $(element);
20 this.options = options;
21 this.addScrollableClass();
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();
33 Scrollable.prototype = {
35 addScrollableClass: function () {
36 if (!this.$element.hasClass("scrollable")) {
37 this.scrollableAdded = true;
38 this.$element.addClass("scrollable");
42 removeScrollableClass: function () {
43 if (this.scrollableAdded)
44 this.$element.removeClass("scrollable");
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);
54 removeSkinClass: function () {
55 if (this.skinClassAdded)
56 this.$element.removeClass(this.options.skin);
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;
66 this.assignOverview();
67 if (this.$overview.length == 0) {
68 this.$viewPort.wrapInner("<div class=\"overview\" />");
69 this.assignOverview();
70 this.overviewAdded = true;
72 this.addScrollBar("vertical", "prepend");
73 this.addScrollBar("horizontal", "append");
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();
85 removeScrollbar: function (orientation) {
86 if (this[orientation + "ScrollbarAdded"])
87 this.$element.find(".scroll-bar." + orientation).remove();
90 assignViewPort: function () {
91 this.$viewPort = this.$element.find(".viewport");
94 assignOverview: function () {
95 this.$overview = this.$viewPort.find(".overview");
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;
105 resize: function (keepPosition) {
107 this.vScrollbar.resize(keepPosition);
109 this.hScrollbar.resize(keepPosition);
112 scrollTo: function (element) {
114 this.vScrollbar.scrollToElement(element);
116 this.hScrollbar.scrollToElement(element);
119 scrollToXY: function (x, y) {
124 scrollToX: function (x) {
126 this.hScrollbar.scrollOverviewTo(x, true);
129 scrollToY: function (y) {
131 this.vScrollbar.scrollOverviewTo(y, true);
134 remove: function () {
135 this.removeScrollableClass();
136 this.removeSkinClass();
137 this.removeScrollbarComponents();
138 this.$element.data("scrollable", null);
139 this.removeKeyboardScrolling();
141 this.vScrollbar.remove();
143 this.hScrollbar.remove();
146 setAnimationSpeed: function (speed) {
147 this.options.animationSpeed = speed;
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())
160 initKeyboardScrolling: function () {
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);
173 .attr('tabindex', '-1')
174 .keydown(this.elementKeydown);
177 removeKeyboardScrolling: function () {
179 .removeAttr('tabindex')
180 .unbind("keydown", this.elementKeydown);
183 bindEvents: function () {
184 if (this.options.onCustomScroll)
185 this.$element.on("customScroll", this.options.onCustomScroll);
190 var Scrollbar = function (scrollable, sizing) {
191 this.scrollable = scrollable;
193 this.$scrollBar = this.sizing.scrollBar(this.scrollable.$element);
194 this.$thumb = this.$scrollBar.find(".thumb");
195 this.setScrollPosition(0, 0);
197 this.initMouseMoveScrolling();
198 this.initMouseWheelScrolling();
199 this.initTouchScrolling();
200 this.initMouseClickScrolling();
201 this.initWindowResize();
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;
221 this.rescroll(keepPosition);
223 this.setScrollPosition(0, 0);
224 this.$scrollBar.toggle(this.enabled);
227 calculateThumbSize: function () {
228 var fixedSize = this.sizing.fixedThumbSize(this.scrollable.options)
233 size = this.ratio * this.viewPortSize
234 return Math.max(size, this.sizing.minSize(this.$thumb));
237 initMouseMoveScrolling: function () {
239 this.$thumb.mousedown(function (event) {
241 _this.startMouseMoveScrolling(event);
243 this.documentMouseup = function (event) {
244 _this.stopMouseMoveScrolling(event);
246 $(document).mouseup(this.documentMouseup);
247 this.documentMousemove = function (event) {
248 _this.mouseMoveScroll(event);
250 $(document).mousemove(this.documentMousemove);
251 this.$thumb.click(function (event) {
252 event.stopPropagation();
256 removeMouseMoveScrolling: function () {
257 this.$thumb.unbind();
258 $(document).unbind("mouseup", this.documentMouseup);
259 $(document).unbind("mousemove", this.documentMousemove);
262 initMouseWheelScrolling: function () {
264 this.scrollable.$element.mousewheel(function (event, delta, deltaX, deltaY) {
266 if (_this.mouseWheelScroll(deltaX, deltaY)) {
267 event.stopPropagation();
268 event.preventDefault();
274 removeMouseWheelScrolling: function () {
275 this.scrollable.$element.unbind("mousewheel");
278 initTouchScrolling: function () {
279 if (document.addEventListener) {
281 this.elementTouchstart = function (event) {
283 _this.startTouchScrolling(event);
285 this.scrollable.$element[0].addEventListener("touchstart", this.elementTouchstart);
286 this.documentTouchmove = function (event) {
287 _this.touchScroll(event);
289 document.addEventListener("touchmove", this.documentTouchmove);
290 this.elementTouchend = function (event) {
291 _this.stopTouchScrolling(event);
293 this.scrollable.$element[0].addEventListener("touchend", this.elementTouchend);
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);
305 initMouseClickScrolling: function () {
307 this.scrollBarClick = function (event) {
308 _this.mouseClickScroll(event);
310 this.$scrollBar.click(this.scrollBarClick);
313 removeMouseClickScrolling: function () {
314 this.$scrollBar.unbind("click", this.scrollBarClick);
317 initWindowResize: function () {
318 if (this.scrollable.options.updateOnWindowResize) {
320 this.windowResize = function () {
323 $(window).resize(this.windowResize);
327 removeWindowResize: function () {
328 $(window).unbind("resize", this.windowResize);
331 isKeyScrolling: function (key) {
332 return this.keyScrollDelta(key) != null;
335 keyScrollDelta: function (key) {
336 for (var scrollingKey in this.sizing.scrollingKeys)
337 if (scrollingKey == key)
338 return this.sizing.scrollingKeys[key](this.viewPortSize);
342 startMouseMoveScrolling: function (event) {
343 this.mouseMoveScrolling = true;
344 $("html").addClass("not-selectable");
345 this.setUnselectable($("html"), "on");
346 this.setScrollEvent(event);
349 stopMouseMoveScrolling: function (event) {
350 this.mouseMoveScrolling = false;
351 $("html").removeClass("not-selectable");
352 this.setUnselectable($("html"), null);
355 setUnselectable: function (element, value) {
356 if (element.attr("unselectable") != value) {
357 element.attr("unselectable", value);
358 element.find(':not(input)').attr('unselectable', value);
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);
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();
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);
383 event.stopPropagation();
384 event.preventDefault();
385 this.setScrollEvent(event.touches[0]);
390 stopTouchScrolling: function (event) {
391 this.touchScrolling = false;
392 event.stopPropagation();
395 mouseWheelScroll: function (deltaX, deltaY) {
396 var delta = -this.sizing.wheelDelta(deltaX, deltaY) * this.scrollable.options.wheelSpeed;
398 return this.scrollOverviewBy(delta);
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
406 this.scrollOverviewBy(delta);
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();
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);
433 thumbPosition: function () {
434 return this.$thumb.position()[this.sizing.offsetComponent()];
437 scrollOverviewBy: function (delta) {
438 var overviewPosition = this.overviewPosition() + delta;
439 return this.scrollOverviewTo(overviewPosition, false);
442 overviewPosition: function () {
443 return -this.scrollable.$overview.position()[this.sizing.offsetComponent()];
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;
452 this.setScrollPositionWithAnimation(overviewPosition, thumbPosition);
454 this.setScrollPosition(overviewPosition, thumbPosition);
455 if (oldScrollPercent != this.scrollPercent) {
456 this.triggerCustomScroll(oldScrollPercent);
463 positionOrMax: function (p, max) {
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
481 rescroll: function (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);
489 var thumbPosition = this.scrollPercent * this.maxThumbPosition;
490 var overviewPosition = this.scrollPercent * this.maxOverviewPosition;
491 this.setScrollPosition(overviewPosition, thumbPosition);
495 setScrollPosition: function (overviewPosition, thumbPosition) {
496 this.$thumb.css(this.sizing.offsetComponent(), thumbPosition + "px");
497 this.scrollable.$overview.css(this.sizing.offsetComponent(), -overviewPosition + "px");
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);
509 calculateMaxThumbPosition: function () {
510 return this.sizing.size(this.$scrollBar) - this.thumbSize;
513 calculateMaxOverviewPosition: function () {
514 return this.sizing.size(this.scrollable.$overview) - this.sizing.size(this.scrollable.$viewPort);
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};
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);
533 remove: function () {
534 this.removeMouseMoveScrolling();
535 this.removeMouseWheelScrolling();
536 this.removeTouchScrolling();
537 this.removeMouseClickScrolling();
538 this.removeWindowResize();
543 var HSizing = function () {
546 HSizing.prototype = {
547 size: function ($el, arg) {
549 return $el.width(arg);
554 minSize: function ($el) {
555 return parseInt($el.css("min-width")) || 0;
558 fixedThumbSize: function (options) {
559 return options.fixedThumbWidth;
562 scrollBar: function ($el) {
563 return $el.find(".scroll-bar.horizontal");
566 mouseDelta: function (event1, event2) {
567 return event2.pageX - event1.pageX;
570 offsetComponent: function () {
574 wheelDelta: function (deltaX, deltaY) {
578 scrollAxis: function () {
582 scrollDirection: function (oldPercent, newPercent) {
583 return oldPercent < newPercent ? "right" : "left";
587 37: function (viewPortSize) {
588 return -10; //arrow left
590 39: function (viewPortSize) {
591 return 10; //arrow right
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());
606 var VSizing = function () {
609 VSizing.prototype = {
611 size: function ($el, arg) {
613 return $el.height(arg);
618 minSize: function ($el) {
619 return parseInt($el.css("min-height")) || 0;
622 fixedThumbSize: function (options) {
623 return options.fixedThumbHeight;
626 scrollBar: function ($el) {
627 return $el.find(".scroll-bar.vertical");
630 mouseDelta: function (event1, event2) {
631 return event2.pageY - event1.pageY;
634 offsetComponent: function () {
638 wheelDelta: function (deltaX, deltaY) {
642 scrollAxis: function () {
646 scrollDirection: function (oldPercent, newPercent) {
647 return oldPercent < newPercent ? "down" : "up";
651 38: function (viewPortSize) {
652 return -10; //arrow up
654 40: function (viewPortSize) {
655 return 10; //arrow down
657 33: function (viewPortSize) {
658 return -(viewPortSize - 20); //page up
660 34: function (viewPortSize) {
661 return viewPortSize - 20; //page down
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());
676 return this.each(function () {
677 if (options == undefined)
678 options = defaultOptions;
679 if (typeof(options) == "string") {
680 var scrollable = $(this).data("scrollable");
682 scrollable[options](args);
684 else if (typeof(options) == "object") {
685 options = $.extend(defaultOptions, options);
686 new Scrollable($(this), options);
689 throw "Invalid type of options";
700 var types = ['DOMMouseScroll', 'mousewheel'];
702 if ($.event.fixHooks) {
703 for (var i = types.length; i;) {
704 $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
708 $.event.special.mousewheel = {
710 if (this.addEventListener) {
711 for (var i = types.length; i;) {
712 this.addEventListener(types[--i], handler, false);
715 this.onmousewheel = handler;
719 teardown: function () {
720 if (this.removeEventListener) {
721 for (var i = types.length; i;) {
722 this.removeEventListener(types[--i], handler, false);
725 this.onmousewheel = null;
731 mousewheel: function (fn) {
732 return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
735 unmousewheel: function (fn) {
736 return this.unbind("mousewheel", fn);
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;
750 if (orgEvent.detail) {
751 delta = -orgEvent.detail / 3;
754 // New school multidimensional scroll (touchpads) deltas
758 if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
764 if (orgEvent.wheelDeltaY !== undefined) {
765 deltaY = orgEvent.wheelDeltaY / 120;
767 if (orgEvent.wheelDeltaX !== undefined) {
768 deltaX = orgEvent.wheelDeltaX / 120;
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);