jquery.scrollbar.js 34 KB

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