You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

441 lines
17 KiB

2 years ago
  1. /*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
  2. * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
  3. * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
  4. *
  5. * Version: 1.3.0
  6. *
  7. */
  8. (function ($) {
  9. jQuery.fn.extend({
  10. slimScroll: function (options) {
  11. var defaults = {
  12. // width in pixels of the visible scroll area
  13. width: 'auto',
  14. // height in pixels of the visible scroll area
  15. height: '100%',
  16. // width in pixels of the scrollbar and rail
  17. size: '7px',
  18. // scrollbar color, accepts any hex/color value
  19. color: '#000',
  20. // scrollbar position - left/right
  21. position: 'right',
  22. // distance in pixels between the side edge and the scrollbar
  23. distance: '1px',
  24. // default scroll position on load - top / bottom / $('selector')
  25. start: 'top',
  26. // sets scrollbar opacity
  27. opacity: .4,
  28. // enables always-on mode for the scrollbar
  29. alwaysVisible: false,
  30. // check if we should hide the scrollbar when user is hovering over
  31. disableFadeOut: false,
  32. // sets visibility of the rail
  33. railVisible: false,
  34. // sets rail color
  35. railColor: '#333',
  36. // sets rail opacity
  37. railOpacity: .2,
  38. // whether we should use jQuery UI Draggable to enable bar dragging
  39. railDraggable: true,
  40. // defautlt CSS class of the slimscroll rail
  41. railClass: 'slimScrollRail',
  42. // defautlt CSS class of the slimscroll bar
  43. barClass: 'slimScrollBar',
  44. // defautlt CSS class of the slimscroll wrapper
  45. wrapperClass: 'slimScrollDiv',
  46. // check if mousewheel should scroll the window if we reach top/bottom
  47. allowPageScroll: false,
  48. // scroll amount applied to each mouse wheel step
  49. wheelStep: 20,
  50. // scroll amount applied when user is using gestures
  51. touchScrollStep: 200,
  52. // sets border radius
  53. borderRadius: '7px',
  54. // sets border radius of the rail
  55. railBorderRadius: '7px'
  56. };
  57. var o = $.extend(defaults, options);
  58. // do it for every element that matches selector
  59. this.each(function () {
  60. var isOverPanel, isOverBar, isDragg, queueHide, touchDif,
  61. barHeight, percentScroll, lastScroll,
  62. divS = '<div></div>',
  63. minBarHeight = 30,
  64. releaseScroll = false;
  65. // used in event handlers and for better minification
  66. var me = $(this);
  67. // ensure we are not binding it again
  68. if (me.parent().hasClass(o.wrapperClass)) {
  69. // start from last bar position
  70. var offset = me.scrollTop();
  71. // find bar and rail
  72. bar = me.parent().find('.' + o.barClass);
  73. rail = me.parent().find('.' + o.railClass);
  74. getBarHeight();
  75. // check if we should scroll existing instance
  76. if ($.isPlainObject(options)) {
  77. // Pass height: auto to an existing slimscroll object to force a resize after contents have changed
  78. if ('height' in options && options.height == 'auto') {
  79. me.parent().css('height', 'auto');
  80. me.css('height', 'auto');
  81. var height = me.parent().parent().height();
  82. me.parent().css('height', height);
  83. me.css('height', height);
  84. }
  85. if ('scrollTo' in options) {
  86. // jump to a static point
  87. offset = parseInt(o.scrollTo);
  88. }
  89. else if ('scrollBy' in options) {
  90. // jump by value pixels
  91. offset += parseInt(o.scrollBy);
  92. }
  93. else if ('destroy' in options) {
  94. // remove slimscroll elements
  95. bar.remove();
  96. rail.remove();
  97. me.unwrap();
  98. return;
  99. }
  100. // scroll content by the given offset
  101. scrollContent(offset, false, true);
  102. }
  103. return;
  104. }
  105. else if ($.isPlainObject(options)) {
  106. if ('destroy' in options) {
  107. return;
  108. }
  109. }
  110. // optionally set height to the parent's height
  111. o.height = (o.height == 'auto') ? me.parent().height() : o.height;
  112. if (o.reduce && typeof o.height === 'string' && o.height.indexOf('px') > -1) {
  113. o.height = (o.height.replace('px', '') - o.reduce) + 'px';
  114. }
  115. // wrap content
  116. var wrapper = $(divS)
  117. .addClass(o.wrapperClass)
  118. .css({
  119. position: 'relative',
  120. overflow: 'hidden',
  121. width: o.width,
  122. height: o.height
  123. });
  124. // update style for the div
  125. me.css({
  126. overflow: 'hidden',
  127. width: o.width,
  128. height: o.height
  129. });
  130. // create scrollbar rail
  131. var rail = $(divS)
  132. .addClass(o.railClass)
  133. .css({
  134. width: o.size,
  135. height: '100%',
  136. position: 'absolute',
  137. top: 0,
  138. display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none',
  139. 'border-radius': o.railBorderRadius,
  140. background: o.railColor,
  141. opacity: o.railOpacity,
  142. zIndex: 90
  143. });
  144. // create scrollbar
  145. var bar = $(divS)
  146. .addClass(o.barClass)
  147. .css({
  148. background: o.color,
  149. width: o.size,
  150. position: 'absolute',
  151. top: 0,
  152. opacity: o.opacity,
  153. display: o.alwaysVisible ? 'block' : 'none',
  154. 'border-radius': o.borderRadius,
  155. BorderRadius: o.borderRadius,
  156. MozBorderRadius: o.borderRadius,
  157. WebkitBorderRadius: o.borderRadius,
  158. zIndex: 99
  159. });
  160. // set position
  161. var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance };
  162. rail.css(posCss);
  163. bar.css(posCss);
  164. // wrap it
  165. me.wrap(wrapper);
  166. // append to parent div
  167. me.parent().append(bar);
  168. me.parent().append(rail);
  169. // make it draggable and no longer dependent on the jqueryUI
  170. if (o.railDraggable) {
  171. bar.bind("mousedown", function (e) {
  172. var $doc = $(document);
  173. isDragg = true;
  174. t = parseFloat(bar.css('top'));
  175. pageY = e.pageY;
  176. $doc.bind("mousemove.slimscroll", function (e) {
  177. currTop = t + e.pageY - pageY;
  178. bar.css('top', currTop);
  179. scrollContent(0, bar.position().top, false);// scroll content
  180. });
  181. $doc.bind("mouseup.slimscroll", function (e) {
  182. isDragg = false; hideBar();
  183. $doc.unbind('.slimscroll');
  184. });
  185. return false;
  186. }).bind("selectstart.slimscroll", function (e) {
  187. e.stopPropagation();
  188. e.preventDefault();
  189. return false;
  190. });
  191. }
  192. // on rail over
  193. rail.hover(function () {
  194. showBar();
  195. }, function () {
  196. hideBar();
  197. });
  198. // on bar over
  199. bar.hover(function () {
  200. isOverBar = true;
  201. }, function () {
  202. isOverBar = false;
  203. });
  204. // show on parent mouseover
  205. me.hover(function () {
  206. isOverPanel = true;
  207. showBar();
  208. hideBar();
  209. }, function () {
  210. isOverPanel = false;
  211. hideBar();
  212. });
  213. // support for mobile
  214. me.bind('touchstart', function (e, b) {
  215. if (e.originalEvent.touches.length) {
  216. // record where touch started
  217. touchDif = e.originalEvent.touches[0].pageY;
  218. }
  219. });
  220. me.bind('touchmove', function (e) {
  221. // prevent scrolling the page if necessary
  222. if (!releaseScroll) {
  223. e.originalEvent.preventDefault();
  224. }
  225. if (e.originalEvent.touches.length) {
  226. // see how far user swiped
  227. var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep;
  228. // scroll content
  229. scrollContent(diff, true);
  230. touchDif = e.originalEvent.touches[0].pageY;
  231. }
  232. });
  233. // set up initial height
  234. getBarHeight();
  235. // check start position
  236. if (o.start === 'bottom') {
  237. // scroll content to bottom
  238. bar.css({ top: me.outerHeight() - bar.outerHeight() });
  239. scrollContent(0, true);
  240. }
  241. else if (o.start !== 'top') {
  242. // assume jQuery selector
  243. scrollContent($(o.start).position().top, null, true);
  244. // make sure bar stays hidden
  245. if (!o.alwaysVisible) { bar.hide(); }
  246. }
  247. // attach scroll events
  248. attachWheel();
  249. function _onWheel(e) {
  250. // use mouse wheel only when mouse is over
  251. if (!isOverPanel) { return; }
  252. var e = e || window.event;
  253. var delta = 0;
  254. if (e.wheelDelta) { delta = -e.wheelDelta / 120; }
  255. if (e.detail) { delta = e.detail / 3; }
  256. var target = e.target || e.srcTarget || e.srcElement;
  257. if ($(target).closest('.' + o.wrapperClass).is(me.parent())) {
  258. // scroll content
  259. scrollContent(delta, true);
  260. }
  261. // stop window scroll
  262. if (e.preventDefault && !releaseScroll) { e.preventDefault(); }
  263. if (!releaseScroll) { e.returnValue = false; }
  264. }
  265. function scrollContent(y, isWheel, isJump) {
  266. releaseScroll = false;
  267. var delta = y;
  268. var maxTop = me.outerHeight() - bar.outerHeight();
  269. if (isWheel) {
  270. // move bar with mouse wheel
  271. delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight();
  272. // move bar, make sure it doesn't go out
  273. delta = Math.min(Math.max(delta, 0), maxTop);
  274. // if scrolling down, make sure a fractional change to the
  275. // scroll position isn't rounded away when the scrollbar's CSS is set
  276. // this flooring of delta would happened automatically when
  277. // bar.css is set below, but we floor here for clarity
  278. delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);
  279. // scroll the scrollbar
  280. bar.css({ top: delta + 'px' });
  281. }
  282. // calculate actual scroll amount
  283. percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight());
  284. delta = percentScroll * (me[0].scrollHeight - me.outerHeight());
  285. if (isJump) {
  286. delta = y;
  287. var offsetTop = delta / me[0].scrollHeight * me.outerHeight();
  288. offsetTop = Math.min(Math.max(offsetTop, 0), maxTop);
  289. bar.css({ top: offsetTop + 'px' });
  290. }
  291. // scroll content
  292. me.scrollTop(delta);
  293. // fire scrolling event
  294. me.trigger('slimscrolling', ~~delta);
  295. // ensure bar is visible
  296. showBar();
  297. // trigger hide when scroll is stopped
  298. hideBar();
  299. }
  300. function attachWheel() {
  301. if (window.addEventListener) {
  302. this.addEventListener('DOMMouseScroll', _onWheel, false);
  303. this.addEventListener('mousewheel', _onWheel, false);
  304. this.addEventListener('MozMousePixelScroll', _onWheel, false);
  305. }
  306. else {
  307. document.attachEvent("onmousewheel", _onWheel)
  308. }
  309. }
  310. function getBarHeight() {
  311. // calculate scrollbar height and make sure it is not too small
  312. barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight);
  313. bar.css({ height: barHeight + 'px' });
  314. // hide scrollbar if content is not long enough
  315. var display = barHeight == me.outerHeight() ? 'none' : 'block';
  316. bar.css({ display: display });
  317. }
  318. function showBar() {
  319. // recalculate bar height
  320. getBarHeight();
  321. clearTimeout(queueHide);
  322. // when bar reached top or bottom
  323. if (percentScroll == ~~percentScroll) {
  324. //release wheel
  325. releaseScroll = o.allowPageScroll;
  326. // publish approporiate event
  327. if (lastScroll != percentScroll) {
  328. var msg = (~~percentScroll == 0) ? 'top' : 'bottom';
  329. me.trigger('slimscroll', msg);
  330. }
  331. }
  332. else {
  333. releaseScroll = false;
  334. }
  335. lastScroll = percentScroll;
  336. // show only when required
  337. if (barHeight >= me.outerHeight()) {
  338. //allow window scroll
  339. releaseScroll = true;
  340. return;
  341. }
  342. bar.stop(true, true).fadeIn('fast');
  343. if (o.railVisible) { rail.stop(true, true).fadeIn('fast'); }
  344. }
  345. function hideBar() {
  346. // only hide when options allow it
  347. if (!o.alwaysVisible) {
  348. queueHide = setTimeout(function () {
  349. if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) {
  350. bar.fadeOut('slow');
  351. rail.fadeOut('slow');
  352. }
  353. }, 1000);
  354. }
  355. }
  356. });
  357. // maintain chainability
  358. return this;
  359. }
  360. });
  361. jQuery.fn.extend({
  362. slimscroll: jQuery.fn.slimScroll
  363. });
  364. })(jQuery);