3 $.fn.customScrollbar = function (options, args) {
9 updateOnWindowResize: false,
11 onCustomScroll: undefined
14 var Scrollable = function (element, options) {
15 this.$element = $(element);
16 this.options = options;
17 this.addScrollableClass();
19 this.addScrollBarComponents();
20 if (this.options.vScroll)
21 this.vScrollbar = new Scrollbar(this, new VSizing());
22 if (this.options.hScroll)
23 this.hScrollbar = new Scrollbar(this, new HSizing());
24 this.$element.data("scrollable", this);
25 this.initKeyboardScrolling();
29 Scrollable.prototype = {
31 addScrollableClass: function () {
32 if (!this.$element.hasClass("scrollable")) {
33 this.scrollableAdded = true;
34 this.$element.addClass("scrollable");
38 removeScrollableClass: function () {
39 if (this.scrollableAdded)
40 this.$element.removeClass("scrollable");
43 addSkinClass: function () {
44 if (typeof(this.options.skin) == "string" && !this.$element.hasClass(this.options.skin)) {
45 this.skinClassAdded = true;
46 this.$element.addClass(this.options.skin);
50 removeSkinClass: function () {
51 if (this.skinClassAdded)
52 this.$element.removeClass(this.options.skin);
55 addScrollBarComponents: function () {
56 this.assignViewPort();
57 if (this.$viewPort.length == 0) {
58 this.$element.wrapInner("<div class=\"viewport\" />");
59 this.assignViewPort();
60 this.viewPortAdded = true;
62 this.assignOverview();
63 if (this.$overview.length == 0) {
64 this.$viewPort.wrapInner("<div class=\"overview\" />");
65 this.assignOverview();
66 this.overviewAdded = true;
68 this.addScrollBar("vertical", "prepend");
69 this.addScrollBar("horizontal", "append");
72 removeScrollbarComponents: function () {
73 this.removeScrollbar("vertical");
74 this.removeScrollbar("horizontal");
75 if (this.overviewAdded)
76 this.$element.unwrap();
77 if (this.viewPortAdded)
78 this.$element.unwrap();
81 removeScrollbar: function (orientation) {
82 if (this[orientation + "ScrollbarAdded"])
83 this.$element.find(".scroll-bar." + orientation).remove();
86 assignViewPort: function () {
87 this.$viewPort = this.$element.find(".viewport");
90 assignOverview: function () {
91 this.$overview = this.$viewPort.find(".overview");
94 addScrollBar: function (orientation, fun) {
95 if (this.$element.find(".scroll-bar." + orientation).length == 0) {
96 this.$element[fun]("<div class='scroll-bar " + orientation + "'><div class='thumb'></div></div>")
97 this[orientation + "ScrollbarAdded"] = true;
101 resize: function (keepPosition) {
103 this.vScrollbar.resize(keepPosition);
105 this.hScrollbar.resize(keepPosition);
108 scrollTo: function (element) {
110 this.vScrollbar.scrollToElement(element);
112 this.hScrollbar.scrollToElement(element);
115 scrollToXY: function (x, y) {
120 scrollToX: function (x) {
122 this.hScrollbar.scrollOverviewTo(x, true);
125 scrollToY: function (y) {
127 this.vScrollbar.scrollOverviewTo(y, true);
130 remove: function () {
131 this.removeScrollableClass();
132 this.removeSkinClass();
133 this.removeScrollbarComponents();
134 this.$element.data("scrollable", null);
135 this.removeKeyboardScrolling();
137 this.vScrollbar.remove();
139 this.hScrollbar.remove();
142 setAnimationSpeed: function (speed) {
143 this.options.animationSpeed = speed;
146 isInside: function (element, wrappingElement) {
147 var $element = $(element);
148 var $wrappingElement = $(wrappingElement);
149 var elementOffset = $element.offset();
150 var wrappingElementOffset = $wrappingElement.offset();
151 return (elementOffset.top >= wrappingElementOffset.top) && (elementOffset.left >= wrappingElementOffset.left) &&
152 (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height()) &&
153 (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width())
156 initKeyboardScrolling: function () {
159 this.elementKeydown = function (event) {
160 if (document.activeElement === _this.$element[0]) {
161 if (_this.vScrollbar)
162 _this.vScrollbar.keyScroll(event);
163 if (_this.hScrollbar)
164 _this.hScrollbar.keyScroll(event);
169 .attr('tabindex', '-1')
170 .keydown(this.elementKeydown);
173 removeKeyboardScrolling: function () {
175 .removeAttr('tabindex')
176 .unbind("keydown", this.elementKeydown);
179 bindEvents: function () {
180 if (this.options.onCustomScroll)
181 this.$element.on("customScroll", this.options.onCustomScroll);
186 var Scrollbar = function (scrollable, sizing) {
187 this.scrollable = scrollable;
189 this.$scrollBar = this.sizing.scrollBar(this.scrollable.$element);
190 this.$thumb = this.$scrollBar.find(".thumb");
191 this.setScrollPosition(0, 0);
193 this.initMouseMoveScrolling();
194 this.initMouseWheelScrolling();
195 this.initTouchScrolling();
196 this.initMouseClickScrolling();
197 this.initWindowResize();
200 Scrollbar.prototype = {
202 resize: function (keepPosition) {
203 this.scrollable.$viewPort.height(this.scrollable.$element.height());
204 this.sizing.size(this.scrollable.$viewPort, this.sizing.size(this.scrollable.$element));
205 this.viewPortSize = this.sizing.size(this.scrollable.$viewPort);
206 this.overviewSize = this.sizing.size(this.scrollable.$overview);
207 this.ratio = this.viewPortSize / this.overviewSize;
208 this.sizing.size(this.$scrollBar, this.viewPortSize);
209 this.thumbSize = Math.max(Math.round(this.ratio * this.viewPortSize), this.sizing.minSize(this.$thumb));
210 this.sizing.size(this.$thumb, this.thumbSize);
211 this.maxThumbPosition = this.calculateMaxThumbPosition();
212 this.maxOverviewPosition = this.calculateMaxOverviewPosition();
213 this.enabled = (this.overviewSize > this.viewPortSize);
214 if (this.scrollPercent === undefined)
215 this.scrollPercent = 0.0;
217 this.rescroll(keepPosition);
219 this.setScrollPosition(0, 0);
220 this.$scrollBar.toggle(this.enabled);
223 initMouseMoveScrolling: function () {
225 this.$thumb.mousedown(function (event) {
227 _this.startMouseMoveScrolling(event);
229 this.documentMouseup = function (event) {
230 _this.stopMouseMoveScrolling(event);
232 $(document).mouseup(this.documentMouseup);
233 this.documentMousemove = function (event) {
234 _this.mouseMoveScroll(event);
236 $(document).mousemove(this.documentMousemove);
237 this.$thumb.click(function (event) {
238 event.stopPropagation();
242 removeMouseMoveScrolling: function () {
243 this.$thumb.unbind();
244 $(document).unbind("mouseup", this.documentMouseup);
245 $(document).unbind("mousemove", this.documentMousemove);
248 initMouseWheelScrolling: function () {
250 this.scrollable.$element.mousewheel(function (event, delta, deltaX, deltaY) {
252 _this.mouseWheelScroll(deltaX, deltaY);
258 removeMouseWheelScrolling: function () {
259 this.scrollable.$element.unbind("mousewheel");
262 initTouchScrolling: function () {
263 if (document.addEventListener) {
265 this.elementTouchstart = function (event) {
267 _this.startTouchScrolling(event);
269 this.scrollable.$element[0].addEventListener("touchstart", this.elementTouchstart);
270 this.documentTouchmove = function (event) {
271 _this.touchScroll(event);
273 document.addEventListener("touchmove", this.documentTouchmove);
274 this.elementTouchend = function (event) {
275 _this.stopTouchScrolling(event);
277 this.scrollable.$element[0].addEventListener("touchend", this.elementTouchend);
281 removeTouchScrolling: function () {
282 if (document.addEventListener) {
283 this.scrollable.$element[0].removeEventListener("touchstart", this.elementTouchstart);
284 document.removeEventListener("touchmove", this.documentTouchmove);
285 this.scrollable.$element[0].removeEventListener("touchend", this.elementTouchend);
289 initMouseClickScrolling: function () {
291 this.scrollBarClick = function (event) {
292 _this.mouseClickScroll(event);
294 this.$scrollBar.click(this.scrollBarClick);
297 removeMouseClickScrolling: function () {
298 this.$scrollBar.unbind("click", this.scrollBarClick);
301 initWindowResize: function () {
302 if (this.scrollable.options.updateOnWindowResize) {
304 this.windowResize = function () {
307 $(window).resize(this.windowResize);
311 removeWindowResize: function () {
312 $(window).unbind("resize", this.windowResize);
315 isKeyScrolling: function (key) {
316 return this.keyScrollDelta(key) != null;
319 keyScrollDelta: function (key) {
320 for (var scrollingKey in this.sizing.scrollingKeys)
321 if (scrollingKey == key)
322 return this.sizing.scrollingKeys[key](this.viewPortSize);
326 startMouseMoveScrolling: function (event) {
327 this.mouseMoveScrolling = true;
328 $("html").addClass("not-selectable");
329 this.setUnselectable($("html"), "on");
330 this.setScrollEvent(event);
333 stopMouseMoveScrolling: function (event) {
334 this.mouseMoveScrolling = false;
335 $("html").removeClass("not-selectable");
336 this.setUnselectable($("html"), null);
339 setUnselectable: function (element, value) {
340 if (element.attr("unselectable") != value) {
341 element.attr("unselectable", value);
342 element.find(':not(input)').attr('unselectable', value);
346 mouseMoveScroll: function (event) {
347 if (this.mouseMoveScrolling) {
348 var delta = this.sizing.mouseDelta(this.scrollEvent, event);
349 this.scrollThumbBy(delta);
350 this.setScrollEvent(event);
354 startTouchScrolling: function (event) {
355 if (event.touches && event.touches.length == 1) {
356 this.setScrollEvent(event.touches[0]);
357 this.touchScrolling = true;
358 event.stopPropagation();
362 touchScroll: function (event) {
363 if (this.touchScrolling && event.touches && event.touches.length == 1) {
364 var delta = -this.sizing.mouseDelta(this.scrollEvent, event.touches[0]);
365 this.scrollOverviewBy(delta);
366 this.setScrollEvent(event.touches[0]);
367 event.stopPropagation();
368 event.preventDefault();
372 stopTouchScrolling: function (event) {
373 this.touchScrolling = false;
374 event.stopPropagation();
377 mouseWheelScroll: function (deltaX, deltaY) {
378 var delta = this.sizing.wheelDelta(deltaX, deltaY) * -10;
380 this.scrollThumbBy(delta);
383 mouseClickScroll: function (event) {
384 var delta = this.viewPortSize - 20;
385 if (event["page" + this.sizing.scrollAxis()] < this.$thumb.offset()[this.sizing.offsetComponent()])
386 // mouse click over thumb
388 this.scrollOverviewBy(delta);
391 keyScroll: function (event) {
392 var keyDown = event.which;
393 if (this.enabled && this.isKeyScrolling(keyDown)) {
394 this.scrollOverviewBy(this.keyScrollDelta(keyDown));
395 event.preventDefault();
399 scrollThumbBy: function (delta) {
400 var thumbPosition = this.thumbPosition();
401 thumbPosition += delta;
402 thumbPosition = this.positionOrMax(thumbPosition, this.maxThumbPosition);
403 var oldScrollPercent = this.scrollPercent;
404 this.scrollPercent = thumbPosition / this.maxThumbPosition;
405 var overviewPosition = (thumbPosition * this.maxOverviewPosition) / this.maxThumbPosition;
406 this.setScrollPosition(overviewPosition, thumbPosition);
407 if (oldScrollPercent != this.scrollPercent)
408 this.triggerCustomScroll(oldScrollPercent);
411 thumbPosition: function () {
412 return this.$thumb.position()[this.sizing.offsetComponent()];
415 scrollOverviewBy: function (delta) {
416 var overviewPosition = this.overviewPosition() + delta;
417 this.scrollOverviewTo(overviewPosition, false);
420 overviewPosition: function () {
421 return -this.scrollable.$overview.position()[this.sizing.offsetComponent()];
424 scrollOverviewTo: function (overviewPosition, animate) {
425 overviewPosition = this.positionOrMax(overviewPosition, this.maxOverviewPosition);
426 var oldScrollPercent = this.scrollPercent;
427 this.scrollPercent = overviewPosition / this.maxOverviewPosition;
428 var thumbPosition = this.scrollPercent * this.maxThumbPosition;
430 this.setScrollPositionWithAnimation(overviewPosition, thumbPosition);
432 this.setScrollPosition(overviewPosition, thumbPosition);
433 if (oldScrollPercent != this.scrollPercent)
434 this.triggerCustomScroll(oldScrollPercent);
437 positionOrMax: function (p, max) {
446 triggerCustomScroll: function (oldScrollPercent) {
447 this.scrollable.$element.trigger("customScroll", {
448 scrollAxis: this.sizing.scrollAxis(),
449 direction: this.sizing.scrollDirection(oldScrollPercent, this.scrollPercent),
450 scrollPercent: this.scrollPercent * 100
455 rescroll: function (keepPosition) {
457 var overviewPosition = this.positionOrMax(this.overviewPosition(), this.maxOverviewPosition);
458 this.scrollPercent = overviewPosition / this.maxOverviewPosition;
459 var thumbPosition = this.scrollPercent * this.maxThumbPosition;
460 this.setScrollPosition(overviewPosition, thumbPosition);
463 var thumbPosition = this.scrollPercent * this.maxThumbPosition;
464 var overviewPosition = this.scrollPercent * this.maxOverviewPosition;
465 this.setScrollPosition(overviewPosition, thumbPosition);
469 setScrollPosition: function (overviewPosition, thumbPosition) {
470 this.$thumb.css(this.sizing.offsetComponent(), thumbPosition + "px");
471 this.scrollable.$overview.css(this.sizing.offsetComponent(), -overviewPosition + "px");
474 setScrollPositionWithAnimation: function (overviewPosition, thumbPosition) {
475 var thumbAnimationOpts = {};
476 var overviewAnimationOpts = {};
477 thumbAnimationOpts[this.sizing.offsetComponent()] = thumbPosition + "px";
478 this.$thumb.animate(thumbAnimationOpts, this.scrollable.options.animationSpeed);
479 overviewAnimationOpts[this.sizing.offsetComponent()] = -overviewPosition + "px";
480 this.scrollable.$overview.animate(overviewAnimationOpts, this.scrollable.options.animationSpeed);
483 calculateMaxThumbPosition: function () {
484 return this.sizing.size(this.$scrollBar) - this.thumbSize;
487 calculateMaxOverviewPosition: function () {
488 return this.sizing.size(this.scrollable.$overview) - this.sizing.size(this.scrollable.$viewPort);
491 setScrollEvent: function (event) {
492 var attr = "page" + this.sizing.scrollAxis();
493 if (!this.scrollEvent || this.scrollEvent[attr] != event[attr])
494 this.scrollEvent = {pageX: event.pageX, pageY: event.pageY};
497 scrollToElement: function (element) {
498 var $element = $(element);
499 if (this.sizing.isInside($element, this.scrollable.$overview) && !this.sizing.isInside($element, this.scrollable.$viewPort)) {
500 var elementOffset = $element.offset();
501 var overviewOffset = this.scrollable.$overview.offset();
502 var viewPortOffset = this.scrollable.$viewPort.offset();
503 this.scrollOverviewTo(elementOffset[this.sizing.offsetComponent()] - overviewOffset[this.sizing.offsetComponent()], true);
507 remove: function () {
508 this.removeMouseMoveScrolling();
509 this.removeMouseWheelScrolling();
510 this.removeTouchScrolling();
511 this.removeMouseClickScrolling();
512 this.removeWindowResize();
517 var HSizing = function () {
520 HSizing.prototype = {
521 size: function ($el, arg) {
523 return $el.width(arg);
528 minSize: function ($el) {
529 return parseInt($el.css("min-width")) || 0;
532 scrollBar: function ($el) {
533 return $el.find(".scroll-bar.horizontal");
536 mouseDelta: function (event1, event2) {
537 return event2.pageX - event1.pageX;
540 offsetComponent: function () {
544 wheelDelta: function (deltaX, deltaY) {
548 scrollAxis: function () {
552 scrollDirection: function (oldPercent, newPercent) {
553 return oldPercent < newPercent ? "right" : "left";
557 37: function (viewPortSize) {
558 return -10; //arrow left
560 39: function (viewPortSize) {
561 return 10; //arrow right
565 isInside: function (element, wrappingElement) {
566 var $element = $(element);
567 var $wrappingElement = $(wrappingElement);
568 var elementOffset = $element.offset();
569 var wrappingElementOffset = $wrappingElement.offset();
570 return (elementOffset.left >= wrappingElementOffset.left) &&
571 (elementOffset.left + $element.width() <= wrappingElementOffset.left + $wrappingElement.width());
576 var VSizing = function () {
579 VSizing.prototype = {
581 size: function ($el, arg) {
583 return $el.height(arg);
588 minSize: function ($el) {
589 return parseInt($el.css("min-height")) || 0;
592 scrollBar: function ($el) {
593 return $el.find(".scroll-bar.vertical");
596 mouseDelta: function (event1, event2) {
597 return event2.pageY - event1.pageY;
600 offsetComponent: function () {
604 wheelDelta: function (deltaX, deltaY) {
608 scrollAxis: function () {
612 scrollDirection: function (oldPercent, newPercent) {
613 return oldPercent < newPercent ? "down" : "up";
617 38: function (viewPortSize) {
618 return -10; //arrow up
620 40: function (viewPortSize) {
621 return 10; //arrow down
623 33: function (viewPortSize) {
624 return -(viewPortSize - 20); //page up
626 34: function (viewPortSize) {
627 return viewPortSize - 20; //page down
631 isInside: function (element, wrappingElement) {
632 var $element = $(element);
633 var $wrappingElement = $(wrappingElement);
634 var elementOffset = $element.offset();
635 var wrappingElementOffset = $wrappingElement.offset();
636 return (elementOffset.top >= wrappingElementOffset.top) &&
637 (elementOffset.top + $element.height() <= wrappingElementOffset.top + $wrappingElement.height());
642 return this.each(function () {
643 if (options == undefined)
644 options = defaultOptions;
645 if (typeof(options) == "string") {
646 var scrollable = $(this).data("scrollable");
648 scrollable[options](args);
650 else if (typeof(options) == "object") {
651 options = $.extend(defaultOptions, options);
652 new Scrollable($(this), options);
655 throw "Invalid type of options";
666 var types = ['DOMMouseScroll', 'mousewheel'];
668 if ($.event.fixHooks) {
669 for (var i = types.length; i;) {
670 $.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
674 $.event.special.mousewheel = {
676 if (this.addEventListener) {
677 for (var i = types.length; i;) {
678 this.addEventListener(types[--i], handler, false);
681 this.onmousewheel = handler;
685 teardown: function () {
686 if (this.removeEventListener) {
687 for (var i = types.length; i;) {
688 this.removeEventListener(types[--i], handler, false);
691 this.onmousewheel = null;
697 mousewheel: function (fn) {
698 return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
701 unmousewheel: function (fn) {
702 return this.unbind("mousewheel", fn);
707 function handler(event) {
708 var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
709 event = $.event.fix(orgEvent);
710 event.type = "mousewheel";
712 // Old school scrollwheel delta
713 if (orgEvent.wheelDelta) {
714 delta = orgEvent.wheelDelta / 120;
716 if (orgEvent.detail) {
717 delta = -orgEvent.detail / 3;
720 // New school multidimensional scroll (touchpads) deltas
724 if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
730 if (orgEvent.wheelDeltaY !== undefined) {
731 deltaY = orgEvent.wheelDeltaY / 120;
733 if (orgEvent.wheelDeltaX !== undefined) {
734 deltaX = orgEvent.wheelDeltaX / 120;
737 // Add event and delta to the front of the arguments
738 args.unshift(event, delta, deltaX, deltaY);
740 return ($.event.dispatch || $.event.handle).apply(this, args);