| 1 |
/** |
| 2 |
* jQuery CSS Customizable Scrollbar |
| 3 |
* |
| 4 |
* Copyright 2015, Yuriy Khabarov |
| 5 |
* Dual licensed under the MIT or GPL Version 2 licenses. |
| 6 |
* |
| 7 |
* If you found bug, please contact me via email <13real008@gmail.com> |
| 8 |
* |
| 9 |
* @author Yuriy Khabarov aka Gromo |
| 10 |
* @version 0.2.10 |
| 11 |
* @url https://github.com/gromo/jquery.scrollbar/ |
| 12 |
* |
| 13 |
*/ |
| 14 |
; |
| 15 |
(function (root, factory) { |
| 16 |
if (typeof define === 'function' && define.amd) { |
| 17 |
define(['jquery'], factory); |
| 18 |
} else { |
| 19 |
factory(root.jQuery); |
| 20 |
} |
| 21 |
}(this, function ($) { |
| 22 |
'use strict'; |
| 23 |
// init flags & variables |
| 24 |
var debug = false; |
| 25 |
|
| 26 |
var browser = { |
| 27 |
data: { |
| 28 |
index: 0, |
| 29 |
name: 'scrollbar' |
| 30 |
}, |
| 31 |
macosx: /mac/i.test(navigator.platform), |
| 32 |
mobile: /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent), |
| 33 |
overlay: null, |
| 34 |
scroll: null, |
| 35 |
scrolls: [], |
| 36 |
webkit: /webkit/i.test(navigator.userAgent) && !/edge\/\d+/i.test(navigator.userAgent) |
| 37 |
}; |
| 38 |
|
| 39 |
browser.scrolls.add = function (instance) { |
| 40 |
this.remove(instance).push(instance); |
| 41 |
}; |
| 42 |
browser.scrolls.remove = function (instance) { |
| 43 |
while ($.inArray(instance, this) >= 0) { |
| 44 |
this.splice($.inArray(instance, this), 1); |
| 45 |
} |
| 46 |
return this; |
| 47 |
}; |
| 48 |
|
| 49 |
var defaults = { |
| 50 |
"autoScrollSize": true, // automatically calculate scrollsize |
| 51 |
"autoUpdate": true, // update scrollbar if content/container size changed |
| 52 |
"debug": false, // debug mode |
| 53 |
"disableBodyScroll": false, // disable body scroll if mouse over container |
| 54 |
"duration": 200, // scroll animate duration in ms |
| 55 |
"ignoreMobile": false, // ignore mobile devices |
| 56 |
"ignoreOverlay": false, // ignore browsers with overlay scrollbars (mobile, MacOS) |
| 57 |
"scrollStep": 30, // scroll step for scrollbar arrows |
| 58 |
"showArrows": false, // add class to show arrows |
| 59 |
"stepScrolling": true, // when scrolling to scrollbar mousedown position |
| 60 |
|
| 61 |
"scrollx": null, // horizontal scroll element |
| 62 |
"scrolly": null, // vertical scroll element |
| 63 |
|
| 64 |
"onDestroy": null, // callback function on destroy, |
| 65 |
"onInit": null, // callback function on first initialization |
| 66 |
"onScroll": null, // callback function on content scrolling |
| 67 |
"onUpdate": null // callback function on init/resize (before scrollbar size calculation) |
| 68 |
}; |
| 69 |
|
| 70 |
|
| 71 |
var BaseScrollbar = function (container) { |
| 72 |
|
| 73 |
if (!browser.scroll) { |
| 74 |
browser.overlay = isScrollOverlaysContent(); |
| 75 |
browser.scroll = getBrowserScrollSize(); |
| 76 |
updateScrollbars(); |
| 77 |
|
| 78 |
$(window).resize(function () { |
| 79 |
var forceUpdate = false; |
| 80 |
if (browser.scroll && (browser.scroll.height || browser.scroll.width)) { |
| 81 |
var scroll = getBrowserScrollSize(); |
| 82 |
if (scroll.height !== browser.scroll.height || scroll.width !== browser.scroll.width) { |
| 83 |
browser.scroll = scroll; |
| 84 |
forceUpdate = true; // handle page zoom |
| 85 |
} |
| 86 |
} |
| 87 |
updateScrollbars(forceUpdate); |
| 88 |
}); |
| 89 |
} |
| 90 |
|
| 91 |
this.container = container; |
| 92 |
this.namespace = '.scrollbar_' + browser.data.index++; |
| 93 |
this.options = $.extend({}, defaults, window.jQueryScrollbarOptions || {}); |
| 94 |
this.scrollTo = null; |
| 95 |
this.scrollx = {}; |
| 96 |
this.scrolly = {}; |
| 97 |
|
| 98 |
container.data(browser.data.name, this); |
| 99 |
browser.scrolls.add(this); |
| 100 |
}; |
| 101 |
|
| 102 |
BaseScrollbar.prototype = { |
| 103 |
|
| 104 |
destroy: function () { |
| 105 |
|
| 106 |
if (!this.wrapper) { |
| 107 |
return; |
| 108 |
} |
| 109 |
|
| 110 |
this.container.removeData(browser.data.name); |
| 111 |
browser.scrolls.remove(this); |
| 112 |
|
| 113 |
// init variables |
| 114 |
var scrollLeft = this.container.scrollLeft(); |
| 115 |
var scrollTop = this.container.scrollTop(); |
| 116 |
|
| 117 |
this.container.insertBefore(this.wrapper).css({ |
| 118 |
"height": "", |
| 119 |
"margin": "", |
| 120 |
"max-height": "" |
| 121 |
}) |
| 122 |
.removeClass('scroll-content scroll-scrollx_visible scroll-scrolly_visible') |
| 123 |
.off(this.namespace) |
| 124 |
.scrollLeft(scrollLeft) |
| 125 |
.scrollTop(scrollTop); |
| 126 |
|
| 127 |
this.scrollx.scroll.removeClass('scroll-scrollx_visible').find('div').andSelf().off(this.namespace); |
| 128 |
this.scrolly.scroll.removeClass('scroll-scrolly_visible').find('div').andSelf().off(this.namespace); |
| 129 |
|
| 130 |
this.wrapper.remove(); |
| 131 |
|
| 132 |
$(document).add('body').off(this.namespace); |
| 133 |
|
| 134 |
if ($.isFunction(this.options.onDestroy)){ |
| 135 |
this.options.onDestroy.apply(this, [this.container]); |
| 136 |
} |
| 137 |
}, |
| 138 |
init: function (options) { |
| 139 |
// init variables |
| 140 |
var S = this, |
| 141 |
c = this.container, |
| 142 |
cw = this.containerWrapper || c, |
| 143 |
namespace = this.namespace, |
| 144 |
o = $.extend(this.options, options || {}), |
| 145 |
s = {x: this.scrollx, y: this.scrolly}, |
| 146 |
w = this.wrapper; |
| 147 |
|
| 148 |
var initScroll = { |
| 149 |
"scrollLeft": c.scrollLeft(), |
| 150 |
"scrollTop": c.scrollTop() |
| 151 |
}; |
| 152 |
|
| 153 |
// do not init if in ignorable browser |
| 154 |
if ((browser.mobile && o.ignoreMobile) |
| 155 |
|| (browser.overlay && o.ignoreOverlay) |
| 156 |
|| (browser.macosx && !browser.webkit) // still required to ignore nonWebKit browsers on Mac |
| 157 |
) { |
| 158 |
//return false; |
| 159 |
} |
| 160 |
|
| 161 |
// init scroll container |
| 162 |
if (!w) { |
| 163 |
this.wrapper = w = $('<div>').addClass('scroll-wrapper').addClass(c.attr('class')) |
| 164 |
.css('position', c.css('position') == 'absolute' ? 'absolute' : 'relative') |
| 165 |
.insertBefore(c).append(c); |
| 166 |
|
| 167 |
if (c.is('textarea')) { |
| 168 |
this.containerWrapper = cw = $('<div>').insertBefore(c).append(c); |
| 169 |
w.addClass('scroll-textarea'); |
| 170 |
} |
| 171 |
|
| 172 |
cw.addClass('scroll-content').css({ |
| 173 |
"height": "auto", |
| 174 |
"margin-bottom": browser.scroll.height * -1 + 'px', |
| 175 |
"margin-right": browser.scroll.width * -1 + 'px', |
| 176 |
"max-height": "" |
| 177 |
}); |
| 178 |
|
| 179 |
c.on('scroll' + namespace, function (event) { |
| 180 |
if ($.isFunction(o.onScroll)) { |
| 181 |
o.onScroll.call(S, { |
| 182 |
"maxScroll": s.y.maxScrollOffset, |
| 183 |
"scroll": c.scrollTop(), |
| 184 |
"size": s.y.size, |
| 185 |
"visible": s.y.visible |
| 186 |
}, { |
| 187 |
"maxScroll": s.x.maxScrollOffset, |
| 188 |
"scroll": c.scrollLeft(), |
| 189 |
"size": s.x.size, |
| 190 |
"visible": s.x.visible |
| 191 |
}); |
| 192 |
} |
| 193 |
s.x.isVisible && s.x.scroll.bar.css('left', c.scrollLeft() * s.x.kx + 'px'); |
| 194 |
s.y.isVisible && s.y.scroll.bar.css('top', c.scrollTop() * s.y.kx + 'px'); |
| 195 |
}); |
| 196 |
|
| 197 |
/* prevent native scrollbars to be visible on #anchor click */ |
| 198 |
w.on('scroll' + namespace, function () { |
| 199 |
w.scrollTop(0).scrollLeft(0); |
| 200 |
}); |
| 201 |
|
| 202 |
if (o.disableBodyScroll) { |
| 203 |
var handleMouseScroll = function (event) { |
| 204 |
isVerticalScroll(event) ? |
| 205 |
s.y.isVisible && s.y.mousewheel(event) : |
| 206 |
s.x.isVisible && s.x.mousewheel(event); |
| 207 |
}; |
| 208 |
w.on('MozMousePixelScroll' + namespace, handleMouseScroll); |
| 209 |
w.on('mousewheel' + namespace, handleMouseScroll); |
| 210 |
|
| 211 |
if (browser.mobile) { |
| 212 |
w.on('touchstart' + namespace, function (event) { |
| 213 |
var touch = event.originalEvent.touches && event.originalEvent.touches[0] || event; |
| 214 |
var originalTouch = { |
| 215 |
"pageX": touch.pageX, |
| 216 |
"pageY": touch.pageY |
| 217 |
}; |
| 218 |
var originalScroll = { |
| 219 |
"left": c.scrollLeft(), |
| 220 |
"top": c.scrollTop() |
| 221 |
}; |
| 222 |
$(document).on('touchmove' + namespace, function (event) { |
| 223 |
var touch = event.originalEvent.targetTouches && event.originalEvent.targetTouches[0] || event; |
| 224 |
c.scrollLeft(originalScroll.left + originalTouch.pageX - touch.pageX); |
| 225 |
c.scrollTop(originalScroll.top + originalTouch.pageY - touch.pageY); |
| 226 |
event.preventDefault(); |
| 227 |
}); |
| 228 |
$(document).on('touchend' + namespace, function () { |
| 229 |
$(document).off(namespace); |
| 230 |
}); |
| 231 |
}); |
| 232 |
} |
| 233 |
} |
| 234 |
if ($.isFunction(o.onInit)){ |
| 235 |
o.onInit.apply(this, [c]); |
| 236 |
} |
| 237 |
|
| 238 |
} else { |
| 239 |
cw.css({ |
| 240 |
"height": "auto", |
| 241 |
"margin-bottom": browser.scroll.height * -1 + 'px', |
| 242 |
"margin-right": browser.scroll.width * -1 + 'px', |
| 243 |
"max-height": "" |
| 244 |
}); |
| 245 |
} |
| 246 |
|
| 247 |
// init scrollbars & recalculate sizes |
| 248 |
$.each(s, function (d, scrollx) { |
| 249 |
|
| 250 |
var scrollCallback = null; |
| 251 |
var scrollForward = 1; |
| 252 |
var scrollOffset = (d === 'x') ? 'scrollLeft' : 'scrollTop'; |
| 253 |
var scrollStep = o.scrollStep; |
| 254 |
var scrollTo = function () { |
| 255 |
var currentOffset = c[scrollOffset](); |
| 256 |
c[scrollOffset](currentOffset + scrollStep); |
| 257 |
if (scrollForward == 1 && (currentOffset + scrollStep) >= scrollToValue) |
| 258 |
currentOffset = c[scrollOffset](); |
| 259 |
if (scrollForward == -1 && (currentOffset + scrollStep) <= scrollToValue) |
| 260 |
currentOffset = c[scrollOffset](); |
| 261 |
if (c[scrollOffset]() == currentOffset && scrollCallback) { |
| 262 |
scrollCallback(); |
| 263 |
} |
| 264 |
} |
| 265 |
var scrollToValue = 0; |
| 266 |
|
| 267 |
if (!scrollx.scroll) { |
| 268 |
|
| 269 |
scrollx.scroll = S._getScroll(o['scroll' + d]).addClass('scroll-' + d); |
| 270 |
|
| 271 |
if(o.showArrows){ |
| 272 |
scrollx.scroll.addClass('scroll-element_arrows_visible'); |
| 273 |
} |
| 274 |
|
| 275 |
scrollx.mousewheel = function (event) { |
| 276 |
|
| 277 |
if (!scrollx.isVisible || (d === 'x' && isVerticalScroll(event))) { |
| 278 |
return true; |
| 279 |
} |
| 280 |
if (d === 'y' && !isVerticalScroll(event)) { |
| 281 |
s.x.mousewheel(event); |
| 282 |
return true; |
| 283 |
} |
| 284 |
|
| 285 |
var delta = event.originalEvent.wheelDelta * -1 || event.originalEvent.detail; |
| 286 |
var maxScrollValue = scrollx.size - scrollx.visible - scrollx.offset; |
| 287 |
|
| 288 |
if ((delta > 0 && scrollToValue < maxScrollValue) || (delta < 0 && scrollToValue > 0)) { |
| 289 |
scrollToValue = scrollToValue + delta; |
| 290 |
if (scrollToValue < 0) |
| 291 |
scrollToValue = 0; |
| 292 |
if (scrollToValue > maxScrollValue) |
| 293 |
scrollToValue = maxScrollValue; |
| 294 |
|
| 295 |
S.scrollTo = S.scrollTo || {}; |
| 296 |
S.scrollTo[scrollOffset] = scrollToValue; |
| 297 |
setTimeout(function () { |
| 298 |
if (S.scrollTo) { |
| 299 |
c.stop().animate(S.scrollTo, 240, 'linear', function () { |
| 300 |
scrollToValue = c[scrollOffset](); |
| 301 |
}); |
| 302 |
S.scrollTo = null; |
| 303 |
} |
| 304 |
}, 1); |
| 305 |
} |
| 306 |
|
| 307 |
event.preventDefault(); |
| 308 |
return false; |
| 309 |
}; |
| 310 |
|
| 311 |
scrollx.scroll |
| 312 |
.on('MozMousePixelScroll' + namespace, scrollx.mousewheel) |
| 313 |
.on('mousewheel' + namespace, scrollx.mousewheel) |
| 314 |
.on('mouseenter' + namespace, function () { |
| 315 |
scrollToValue = c[scrollOffset](); |
| 316 |
}); |
| 317 |
|
| 318 |
// handle arrows & scroll inner mousedown event |
| 319 |
scrollx.scroll.find('.scroll-arrow, .scroll-element_track') |
| 320 |
.on('mousedown' + namespace, function (event) { |
| 321 |
|
| 322 |
if (event.which != 1) // lmb |
| 323 |
return true; |
| 324 |
|
| 325 |
scrollForward = 1; |
| 326 |
|
| 327 |
var data = { |
| 328 |
"eventOffset": event[(d === 'x') ? 'pageX' : 'pageY'], |
| 329 |
"maxScrollValue": scrollx.size - scrollx.visible - scrollx.offset, |
| 330 |
"scrollbarOffset": scrollx.scroll.bar.offset()[(d === 'x') ? 'left' : 'top'], |
| 331 |
"scrollbarSize": scrollx.scroll.bar[(d === 'x') ? 'outerWidth' : 'outerHeight']() |
| 332 |
}; |
| 333 |
var timeout = 0, timer = 0; |
| 334 |
|
| 335 |
if ($(this).hasClass('scroll-arrow')) { |
| 336 |
scrollForward = $(this).hasClass("scroll-arrow_more") ? 1 : -1; |
| 337 |
scrollStep = o.scrollStep * scrollForward; |
| 338 |
scrollToValue = scrollForward > 0 ? data.maxScrollValue : 0; |
| 339 |
} else { |
| 340 |
scrollForward = (data.eventOffset > (data.scrollbarOffset + data.scrollbarSize) ? 1 |
| 341 |
: (data.eventOffset < data.scrollbarOffset ? -1 : 0)); |
| 342 |
scrollStep = Math.round(scrollx.visible * 0.75) * scrollForward; |
| 343 |
scrollToValue = (data.eventOffset - data.scrollbarOffset - |
| 344 |
(o.stepScrolling ? (scrollForward == 1 ? data.scrollbarSize : 0) |
| 345 |
: Math.round(data.scrollbarSize / 2))); |
| 346 |
scrollToValue = c[scrollOffset]() + (scrollToValue / scrollx.kx); |
| 347 |
} |
| 348 |
|
| 349 |
S.scrollTo = S.scrollTo || {}; |
| 350 |
S.scrollTo[scrollOffset] = o.stepScrolling ? c[scrollOffset]() + scrollStep : scrollToValue; |
| 351 |
|
| 352 |
if (o.stepScrolling) { |
| 353 |
scrollCallback = function () { |
| 354 |
scrollToValue = c[scrollOffset](); |
| 355 |
clearInterval(timer); |
| 356 |
clearTimeout(timeout); |
| 357 |
timeout = 0; |
| 358 |
timer = 0; |
| 359 |
}; |
| 360 |
timeout = setTimeout(function () { |
| 361 |
timer = setInterval(scrollTo, 40); |
| 362 |
}, o.duration + 100); |
| 363 |
} |
| 364 |
|
| 365 |
setTimeout(function () { |
| 366 |
if (S.scrollTo) { |
| 367 |
c.animate(S.scrollTo, o.duration); |
| 368 |
S.scrollTo = null; |
| 369 |
} |
| 370 |
}, 1); |
| 371 |
|
| 372 |
return S._handleMouseDown(scrollCallback, event); |
| 373 |
}); |
| 374 |
|
| 375 |
// handle scrollbar drag'n'drop |
| 376 |
scrollx.scroll.bar.on('mousedown' + namespace, function (event) { |
| 377 |
|
| 378 |
if (event.which != 1) // lmb |
| 379 |
return true; |
| 380 |
|
| 381 |
var eventPosition = event[(d === 'x') ? 'pageX' : 'pageY']; |
| 382 |
var initOffset = c[scrollOffset](); |
| 383 |
|
| 384 |
scrollx.scroll.addClass('scroll-draggable'); |
| 385 |
|
| 386 |
$(document).on('mousemove' + namespace, function (event) { |
| 387 |
var diff = parseInt((event[(d === 'x') ? 'pageX' : 'pageY'] - eventPosition) / scrollx.kx, 10); |
| 388 |
c[scrollOffset](initOffset + diff); |
| 389 |
}); |
| 390 |
|
| 391 |
return S._handleMouseDown(function () { |
| 392 |
scrollx.scroll.removeClass('scroll-draggable'); |
| 393 |
scrollToValue = c[scrollOffset](); |
| 394 |
}, event); |
| 395 |
}); |
| 396 |
} |
| 397 |
}); |
| 398 |
|
| 399 |
// remove classes & reset applied styles |
| 400 |
$.each(s, function (d, scrollx) { |
| 401 |
var scrollClass = 'scroll-scroll' + d + '_visible'; |
| 402 |
var scrolly = (d == "x") ? s.y : s.x; |
| 403 |
|
| 404 |
scrollx.scroll.removeClass(scrollClass); |
| 405 |
scrolly.scroll.removeClass(scrollClass); |
| 406 |
cw.removeClass(scrollClass); |
| 407 |
}); |
| 408 |
|
| 409 |
// calculate init sizes |
| 410 |
$.each(s, function (d, scrollx) { |
| 411 |
$.extend(scrollx, (d == "x") ? { |
| 412 |
"offset": parseInt(c.css('left'), 10) || 0, |
| 413 |
"size": c.prop('scrollWidth'), |
| 414 |
"visible": w.width() |
| 415 |
} : { |
| 416 |
"offset": parseInt(c.css('top'), 10) || 0, |
| 417 |
"size": c.prop('scrollHeight'), |
| 418 |
"visible": w.height() |
| 419 |
}); |
| 420 |
}); |
| 421 |
|
| 422 |
// update scrollbar visibility/dimensions |
| 423 |
this._updateScroll('x', this.scrollx); |
| 424 |
this._updateScroll('y', this.scrolly); |
| 425 |
|
| 426 |
if ($.isFunction(o.onUpdate)){ |
| 427 |
o.onUpdate.apply(this, [c]); |
| 428 |
} |
| 429 |
|
| 430 |
// calculate scroll size |
| 431 |
$.each(s, function (d, scrollx) { |
| 432 |
|
| 433 |
var cssOffset = (d === 'x') ? 'left' : 'top'; |
| 434 |
var cssFullSize = (d === 'x') ? 'outerWidth' : 'outerHeight'; |
| 435 |
var cssSize = (d === 'x') ? 'width' : 'height'; |
| 436 |
var offset = parseInt(c.css(cssOffset), 10) || 0; |
| 437 |
|
| 438 |
var AreaSize = scrollx.size; |
| 439 |
var AreaVisible = scrollx.visible + offset; |
| 440 |
|
| 441 |
var scrollSize = scrollx.scroll.size[cssFullSize]() + (parseInt(scrollx.scroll.size.css(cssOffset), 10) || 0); |
| 442 |
|
| 443 |
if (o.autoScrollSize) { |
| 444 |
scrollx.scrollbarSize = parseInt(scrollSize * AreaVisible / AreaSize, 10); |
| 445 |
scrollx.scroll.bar.css(cssSize, scrollx.scrollbarSize + 'px'); |
| 446 |
} |
| 447 |
|
| 448 |
scrollx.scrollbarSize = scrollx.scroll.bar[cssFullSize](); |
| 449 |
scrollx.kx = ((scrollSize - scrollx.scrollbarSize) / (AreaSize - AreaVisible)) || 1; |
| 450 |
scrollx.maxScrollOffset = AreaSize - AreaVisible; |
| 451 |
}); |
| 452 |
|
| 453 |
c.scrollLeft(initScroll.scrollLeft).scrollTop(initScroll.scrollTop).trigger('scroll'); |
| 454 |
}, |
| 455 |
|
| 456 |
/** |
| 457 |
* Get scrollx/scrolly object |
| 458 |
* |
| 459 |
* @param {Mixed} scroll |
| 460 |
* @returns {jQuery} scroll object |
| 461 |
*/ |
| 462 |
_getScroll: function (scroll) { |
| 463 |
var types = { |
| 464 |
advanced: [ |
| 465 |
'<div class="scroll-element">', |
| 466 |
'<div class="scroll-element_corner"></div>', |
| 467 |
'<div class="scroll-arrow scroll-arrow_less"></div>', |
| 468 |
'<div class="scroll-arrow scroll-arrow_more"></div>', |
| 469 |
'<div class="scroll-element_outer">', |
| 470 |
'<div class="scroll-element_size"></div>', // required! used for scrollbar size calculation ! |
| 471 |
'<div class="scroll-element_inner-wrapper">', |
| 472 |
'<div class="scroll-element_inner scroll-element_track">', // used for handling scrollbar click |
| 473 |
'<div class="scroll-element_inner-bottom"></div>', |
| 474 |
'</div>', |
| 475 |
'</div>', |
| 476 |
'<div class="scroll-bar">', // required |
| 477 |
'<div class="scroll-bar_body">', |
| 478 |
'<div class="scroll-bar_body-inner"></div>', |
| 479 |
'</div>', |
| 480 |
'<div class="scroll-bar_bottom"></div>', |
| 481 |
'<div class="scroll-bar_center"></div>', |
| 482 |
'</div>', |
| 483 |
'</div>', |
| 484 |
'</div>' |
| 485 |
].join(''), |
| 486 |
simple: [ |
| 487 |
'<div class="scroll-element">', |
| 488 |
'<div class="scroll-element_outer">', |
| 489 |
'<div class="scroll-element_size"></div>', // required! used for scrollbar size calculation ! |
| 490 |
'<div class="scroll-element_track"></div>', // used for handling scrollbar click |
| 491 |
'<div class="scroll-bar"></div>', // required |
| 492 |
'</div>', |
| 493 |
'</div>' |
| 494 |
].join('') |
| 495 |
}; |
| 496 |
if (types[scroll]) { |
| 497 |
scroll = types[scroll]; |
| 498 |
} |
| 499 |
if (!scroll) { |
| 500 |
scroll = types['simple']; |
| 501 |
} |
| 502 |
if (typeof (scroll) == 'string') { |
| 503 |
scroll = $(scroll).appendTo(this.wrapper); |
| 504 |
} else { |
| 505 |
scroll = $(scroll); |
| 506 |
} |
| 507 |
$.extend(scroll, { |
| 508 |
bar: scroll.find('.scroll-bar'), |
| 509 |
size: scroll.find('.scroll-element_size'), |
| 510 |
track: scroll.find('.scroll-element_track') |
| 511 |
}); |
| 512 |
return scroll; |
| 513 |
}, |
| 514 |
|
| 515 |
_handleMouseDown: function(callback, event) { |
| 516 |
|
| 517 |
var namespace = this.namespace; |
| 518 |
|
| 519 |
$(document).on('blur' + namespace, function () { |
| 520 |
$(document).add('body').off(namespace); |
| 521 |
callback && callback(); |
| 522 |
}); |
| 523 |
$(document).on('dragstart' + namespace, function (event) { |
| 524 |
event.preventDefault(); |
| 525 |
return false; |
| 526 |
}); |
| 527 |
$(document).on('mouseup' + namespace, function () { |
| 528 |
$(document).add('body').off(namespace); |
| 529 |
callback && callback(); |
| 530 |
}); |
| 531 |
$('body').on('selectstart' + namespace, function (event) { |
| 532 |
event.preventDefault(); |
| 533 |
return false; |
| 534 |
}); |
| 535 |
|
| 536 |
event && event.preventDefault(); |
| 537 |
return false; |
| 538 |
}, |
| 539 |
|
| 540 |
_updateScroll: function (d, scrollx) { |
| 541 |
|
| 542 |
var container = this.container, |
| 543 |
containerWrapper = this.containerWrapper || container, |
| 544 |
scrollClass = 'scroll-scroll' + d + '_visible', |
| 545 |
scrolly = (d === 'x') ? this.scrolly : this.scrollx, |
| 546 |
offset = parseInt(this.container.css((d === 'x') ? 'left' : 'top'), 10) || 0, |
| 547 |
wrapper = this.wrapper; |
| 548 |
|
| 549 |
var AreaSize = scrollx.size; |
| 550 |
var AreaVisible = scrollx.visible + offset; |
| 551 |
|
| 552 |
scrollx.isVisible = (AreaSize - AreaVisible) > 1; // bug in IE9/11 with 1px diff |
| 553 |
if (scrollx.isVisible) { |
| 554 |
scrollx.scroll.addClass(scrollClass); |
| 555 |
scrolly.scroll.addClass(scrollClass); |
| 556 |
containerWrapper.addClass(scrollClass); |
| 557 |
} else { |
| 558 |
scrollx.scroll.removeClass(scrollClass); |
| 559 |
scrolly.scroll.removeClass(scrollClass); |
| 560 |
containerWrapper.removeClass(scrollClass); |
| 561 |
} |
| 562 |
|
| 563 |
if (d === 'y') { |
| 564 |
if(container.is('textarea') || AreaSize < AreaVisible){ |
| 565 |
containerWrapper.css({ |
| 566 |
"height": (AreaVisible + browser.scroll.height) + 'px', |
| 567 |
"max-height": "none" |
| 568 |
}); |
| 569 |
} else { |
| 570 |
containerWrapper.css({ |
| 571 |
//"height": "auto", // do not reset height value: issue with height:100%! |
| 572 |
"max-height": (AreaVisible + browser.scroll.height) + 'px' |
| 573 |
}); |
| 574 |
} |
| 575 |
} |
| 576 |
|
| 577 |
if (scrollx.size != container.prop('scrollWidth') |
| 578 |
|| scrolly.size != container.prop('scrollHeight') |
| 579 |
|| scrollx.visible != wrapper.width() |
| 580 |
|| scrolly.visible != wrapper.height() |
| 581 |
|| scrollx.offset != (parseInt(container.css('left'), 10) || 0) |
| 582 |
|| scrolly.offset != (parseInt(container.css('top'), 10) || 0) |
| 583 |
) { |
| 584 |
$.extend(this.scrollx, { |
| 585 |
"offset": parseInt(container.css('left'), 10) || 0, |
| 586 |
"size": container.prop('scrollWidth'), |
| 587 |
"visible": wrapper.width() |
| 588 |
}); |
| 589 |
$.extend(this.scrolly, { |
| 590 |
"offset": parseInt(container.css('top'), 10) || 0, |
| 591 |
"size": this.container.prop('scrollHeight'), |
| 592 |
"visible": wrapper.height() |
| 593 |
}); |
| 594 |
this._updateScroll(d === 'x' ? 'y' : 'x', scrolly); |
| 595 |
} |
| 596 |
} |
| 597 |
}; |
| 598 |
|
| 599 |
var CustomScrollbar = BaseScrollbar; |
| 600 |
|
| 601 |
/* |
| 602 |
* Extend jQuery as plugin |
| 603 |
* |
| 604 |
* @param {Mixed} command to execute |
| 605 |
* @param {Mixed} arguments as Array |
| 606 |
* @return {jQuery} |
| 607 |
*/ |
| 608 |
$.fn.scrollbar = function (command, args) { |
| 609 |
if (typeof command !== 'string') { |
| 610 |
args = command; |
| 611 |
command = 'init'; |
| 612 |
} |
| 613 |
if (typeof args === 'undefined') { |
| 614 |
args = []; |
| 615 |
} |
| 616 |
if (!$.isArray(args)) { |
| 617 |
args = [args]; |
| 618 |
} |
| 619 |
this.not('body, .scroll-wrapper').each(function () { |
| 620 |
var element = $(this), |
| 621 |
instance = element.data(browser.data.name); |
| 622 |
if (instance || command === 'init') { |
| 623 |
if (!instance) { |
| 624 |
instance = new CustomScrollbar(element); |
| 625 |
} |
| 626 |
if (instance[command]) { |
| 627 |
instance[command].apply(instance, args); |
| 628 |
} |
| 629 |
} |
| 630 |
}); |
| 631 |
return this; |
| 632 |
}; |
| 633 |
|
| 634 |
/** |
| 635 |
* Connect default options to global object |
| 636 |
*/ |
| 637 |
$.fn.scrollbar.options = defaults; |
| 638 |
|
| 639 |
|
| 640 |
/** |
| 641 |
* Check if scroll content/container size is changed |
| 642 |
*/ |
| 643 |
|
| 644 |
var updateScrollbars = (function () { |
| 645 |
var timer = 0, |
| 646 |
timerCounter = 0; |
| 647 |
|
| 648 |
return function (force) { |
| 649 |
var i, container, options, scroll, wrapper, scrollx, scrolly; |
| 650 |
for (i = 0; i < browser.scrolls.length; i++) { |
| 651 |
scroll = browser.scrolls[i]; |
| 652 |
container = scroll.container; |
| 653 |
options = scroll.options; |
| 654 |
wrapper = scroll.wrapper; |
| 655 |
scrollx = scroll.scrollx; |
| 656 |
scrolly = scroll.scrolly; |
| 657 |
if (force || (options.autoUpdate && wrapper && wrapper.is(':visible') && |
| 658 |
(container.prop('scrollWidth') != scrollx.size || container.prop('scrollHeight') != scrolly.size || wrapper.width() != scrollx.visible || wrapper.height() != scrolly.visible))) { |
| 659 |
scroll.init(); |
| 660 |
|
| 661 |
if (options.debug) { |
| 662 |
window.console && console.log({ |
| 663 |
scrollHeight: container.prop('scrollHeight') + ':' + scroll.scrolly.size, |
| 664 |
scrollWidth: container.prop('scrollWidth') + ':' + scroll.scrollx.size, |
| 665 |
visibleHeight: wrapper.height() + ':' + scroll.scrolly.visible, |
| 666 |
visibleWidth: wrapper.width() + ':' + scroll.scrollx.visible |
| 667 |
}, true); |
| 668 |
timerCounter++; |
| 669 |
} |
| 670 |
} |
| 671 |
} |
| 672 |
if (debug && timerCounter > 10) { |
| 673 |
window.console && console.log('Scroll updates exceed 10'); |
| 674 |
updateScrollbars = function () {}; |
| 675 |
} else { |
| 676 |
clearTimeout(timer); |
| 677 |
timer = setTimeout(updateScrollbars, 300); |
| 678 |
} |
| 679 |
}; |
| 680 |
})(); |
| 681 |
|
| 682 |
/* ADDITIONAL FUNCTIONS */ |
| 683 |
/** |
| 684 |
* Get native browser scrollbar size (height/width) |
| 685 |
* |
| 686 |
* @param {Boolean} actual size or CSS size, default - CSS size |
| 687 |
* @returns {Object} with height, width |
| 688 |
*/ |
| 689 |
function getBrowserScrollSize(actualSize) { |
| 690 |
|
| 691 |
if (browser.webkit && !actualSize) { |
| 692 |
return { |
| 693 |
"height": 0, |
| 694 |
"width": 0 |
| 695 |
}; |
| 696 |
} |
| 697 |
|
| 698 |
if (!browser.data.outer) { |
| 699 |
var css = { |
| 700 |
"border": "none", |
| 701 |
"box-sizing": "content-box", |
| 702 |
"height": "200px", |
| 703 |
"margin": "0", |
| 704 |
"padding": "0", |
| 705 |
"width": "200px" |
| 706 |
}; |
| 707 |
browser.data.inner = $("<div>").css($.extend({}, css)); |
| 708 |
browser.data.outer = $("<div>").css($.extend({ |
| 709 |
"left": "-1000px", |
| 710 |
"overflow": "scroll", |
| 711 |
"position": "absolute", |
| 712 |
"top": "-1000px" |
| 713 |
}, css)).append(browser.data.inner).appendTo("body"); |
| 714 |
} |
| 715 |
|
| 716 |
browser.data.outer.scrollLeft(1000).scrollTop(1000); |
| 717 |
|
| 718 |
return { |
| 719 |
"height": Math.ceil((browser.data.outer.offset().top - browser.data.inner.offset().top) || 0), |
| 720 |
"width": Math.ceil((browser.data.outer.offset().left - browser.data.inner.offset().left) || 0) |
| 721 |
}; |
| 722 |
} |
| 723 |
|
| 724 |
/** |
| 725 |
* Check if native browser scrollbars overlay content |
| 726 |
* |
| 727 |
* @returns {Boolean} |
| 728 |
*/ |
| 729 |
function isScrollOverlaysContent() { |
| 730 |
var scrollSize = getBrowserScrollSize(true); |
| 731 |
return !(scrollSize.height || scrollSize.width); |
| 732 |
} |
| 733 |
|
| 734 |
function isVerticalScroll(event) { |
| 735 |
var e = event.originalEvent; |
| 736 |
if (e.axis && e.axis === e.HORIZONTAL_AXIS) |
| 737 |
return false; |
| 738 |
if (e.wheelDeltaX) |
| 739 |
return false; |
| 740 |
return true; |
| 741 |
} |
| 742 |
|
| 743 |
|
| 744 |
/** |
| 745 |
* Extend AngularJS as UI directive |
| 746 |
* and expose a provider for override default config |
| 747 |
* |
| 748 |
*/ |
| 749 |
if (window.angular) { |
| 750 |
(function (angular) { |
| 751 |
angular.module('jQueryScrollbar', []) |
| 752 |
.provider('jQueryScrollbar', function () { |
| 753 |
var defaultOptions = defaults; |
| 754 |
return { |
| 755 |
setOptions: function (options) { |
| 756 |
angular.extend(defaultOptions, options); |
| 757 |
}, |
| 758 |
$get: function () { |
| 759 |
return { |
| 760 |
options: angular.copy(defaultOptions) |
| 761 |
}; |
| 762 |
} |
| 763 |
}; |
| 764 |
}) |
| 765 |
.directive('jqueryScrollbar', ['jQueryScrollbar', '$parse', function (jQueryScrollbar, $parse) { |
| 766 |
return { |
| 767 |
"restrict": "AC", |
| 768 |
"link": function (scope, element, attrs) { |
| 769 |
var model = $parse(attrs.jqueryScrollbar), |
| 770 |
options = model(scope); |
| 771 |
element.scrollbar(options || jQueryScrollbar.options) |
| 772 |
.on('$destroy', function () { |
| 773 |
element.scrollbar('destroy'); |
| 774 |
}); |
| 775 |
} |
| 776 |
}; |
| 777 |
}]); |
| 778 |
})(window.angular); |
| 779 |
} |
| 780 |
})); |
| 781 |
|