jquery.scrollbar.js 31 KB

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