index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. var SlimScroll = (function () {
  2. function $(el) {
  3. if (!(this instanceof $)) {
  4. return new $(el)
  5. }
  6. if (typeof el === 'string') {
  7. var r = /<(\w+)><\/\1>$/.exec(el)
  8. if (r) {
  9. el = document.createElement(r[1])
  10. } else {
  11. el = document.querySelector(el)
  12. }
  13. }
  14. this.el = (el && el.nodeType === 1) ? el : document.documentElement
  15. return this
  16. }
  17. $.prototype = {
  18. parent: function () {
  19. return $(this.el.parentNode || this.el.parentElement)
  20. },
  21. closest: function (selector) {
  22. if (!selector) return $(document)
  23. var parent = this.parent()
  24. while (parent.el !== $(selector).el) {
  25. parent = parent.parent()
  26. }
  27. return parent
  28. },
  29. is: function (obj) {
  30. if (this.el === obj.el) {
  31. return true
  32. }
  33. return false
  34. },
  35. hasClass: function (className) {
  36. if (this.el.className.indexOf(className) >= 0) {
  37. return true
  38. }
  39. return false
  40. },
  41. addClass: function (className) {
  42. if (!className || typeof className === 'undefined') return
  43. if (this.hasClass(className)) return
  44. var cls = this.el.className.split(' ')
  45. cls.push(className)
  46. this.el.className = cls.join(' ').trim()
  47. return this
  48. },
  49. css: function (styleObj) {
  50. if (typeof styleObj === 'string') {
  51. return this.el.style[styleObj].replace('px', '')
  52. }
  53. for (var key in styleObj) {
  54. if (typeof styleObj[key] === 'number' && parseInt(styleObj[key])) styleObj[key] = parseInt(styleObj[key]) + 'px'
  55. if (key === 'zIndex') styleObj[key] = parseInt(styleObj[key])
  56. this.el.style[key] = styleObj[key]
  57. }
  58. return this
  59. },
  60. show: function () {
  61. this.el.style.display = 'block'
  62. },
  63. hide: function () {
  64. this.el.style.display = 'none'
  65. },
  66. wrap: function (obj) {
  67. this.parent().el.insertBefore(obj.el, this.el)
  68. obj.append(this)
  69. return this
  70. },
  71. append: function (obj) {
  72. this.el.appendChild(obj.el)
  73. return this
  74. },
  75. scrollTop: function (y) {
  76. if (typeof y !== 'undefined') {
  77. this.el.scrollTop = parseInt(y)
  78. return this
  79. }
  80. return this.el.scrollTop
  81. },
  82. outerHeight: function () {
  83. return this.el.offsetHeight || this.el.clientHeight
  84. },
  85. hover: function (hoverIn, hoverOut) {
  86. this.bind('mouseenter', hoverIn)
  87. this.bind('mouseleave', hoverOut)
  88. },
  89. bind: function (type, fn, capture) {
  90. var el = this.el;
  91. if (window.addEventListener) {
  92. el.addEventListener(type, fn, capture);
  93. var ev = document.createEvent('HTMLEvents');
  94. ev.initEvent(type, capture || false, false);
  95. // 在元素上存储创建的事件,方便自定义触发
  96. if (!el['ev' + type]) {
  97. el['ev' + type] = ev;
  98. }
  99. } else if (window.attachEvent) {
  100. el.attachEvent('on' + type, fn);
  101. if (isNaN(el['cu' + type])) {
  102. // 自定义属性,触发事件用
  103. el['cu' + type] = 0;
  104. }
  105. var fnEv = function (event) {
  106. if (event.propertyName === 'cu' + type) {
  107. fn.call(el);
  108. }
  109. };
  110. el.attachEvent('onpropertychange', fnEv);
  111. // 在元素上存储绑定的propertychange事件,方便删除
  112. if (!el['ev' + type]) {
  113. el['ev' + type] = [fnEv];
  114. } else {
  115. el['ev' + type].push(fnEv);
  116. }
  117. }
  118. return this;
  119. },
  120. trigger: function (type) {
  121. var el = this.el;
  122. if (typeof type === 'string') {
  123. if (document.dispatchEvent) {
  124. if (el['ev' + type]) {
  125. el.dispatchEvent(el['ev' + type]);
  126. }
  127. } else if (document.attachEvent) {
  128. // 改变对应自定义属性,触发自定义事件
  129. el['cu' + type]++;
  130. }
  131. }
  132. return this;
  133. },
  134. unbind: function (type, fn, capture) {
  135. var el = this.el;
  136. if (window.removeEventListener) {
  137. el.removeEventListener(type, fn, capture || false);
  138. } else if (document.attachEvent) {
  139. el.detachEvent('on' + type, fn);
  140. var arrEv = el['ev' + type];
  141. if (arrEv instanceof Array) {
  142. for (var i = 0; i < arrEv.length; i += 1) {
  143. // 删除该方法名下所有绑定的propertychange事件
  144. el.detachEvent('onpropertychange', arrEv[i]);
  145. }
  146. }
  147. }
  148. return this;
  149. }
  150. }
  151. $.extend = function (deep) {
  152. var start = 1
  153. if (typeof deep === 'object') {
  154. start = 0
  155. }
  156. var objs = Array.prototype.slice.call(arguments, start),
  157. newObj = {}
  158. for (var i = 0; i < objs.length; i++) {
  159. if (typeof objs !== 'object') return
  160. for (var key in objs[i]) {
  161. newObj[key] = typeof objs[i][key] === 'object' && deep === true ? $.extend(true, objs[i][key]) : objs[i][key]
  162. }
  163. }
  164. return newObj
  165. }
  166. $.isPlainObject = function (obj) {
  167. return typeof obj === 'object' && !(obj instanceof Array)
  168. }
  169. function SlimScroll(el, options) {
  170. var defaults = {
  171. // width in pixels of the visible scroll area
  172. width: 'auto',
  173. // height in pixels of the visible scroll area
  174. height: '250px',
  175. // width in pixels of the scrollbar and rail
  176. size: '7px',
  177. // scrollbar color, accepts any hex/color value
  178. color: '#000',
  179. // scrollbar position - left/right
  180. position: 'right',
  181. // distance in pixels between the side edge and the scrollbar
  182. distance: '1px',
  183. // default scroll position on load - top / bottom / $('selector')
  184. start: 'top',
  185. // sets scrollbar opacity
  186. opacity: 0.4,
  187. // enables always-on mode for the scrollbar
  188. alwaysVisible: false,
  189. // check if we should hide the scrollbar when user is hovering over
  190. disableFadeOut: false,
  191. // sets visibility of the rail
  192. railVisible: false,
  193. // sets rail color
  194. railColor: '#333',
  195. // sets rail opacity
  196. railOpacity: 0.2,
  197. // whether we should use jQuery UI Draggable to enable bar dragging
  198. railDraggable: true,
  199. // defautlt CSS class of the slimscroll rail
  200. railClass: 'slimScrollRail',
  201. // defautlt CSS class of the slimscroll bar
  202. barClass: 'slimScrollBar',
  203. // defautlt CSS class of the slimscroll wrapper
  204. wrapperClass: 'slimScrollDiv',
  205. // check if mousewheel should scroll the window if we reach top/bottom
  206. allowPageScroll: false,
  207. // scroll amount applied to each mouse wheel step
  208. wheelStep: 20,
  209. // scroll amount applied when user is using gestures
  210. touchScrollStep: 200,
  211. // sets border radius
  212. borderRadius: '7px',
  213. // sets border radius of the rail
  214. railBorderRadius: '7px'
  215. };
  216. var o = $.extend(defaults, options)
  217. // do it for every element that matches selector
  218. // this.each(function () {
  219. var isOverPanel, isOverBar, isDragg, queueHide, touchDif,
  220. barHeight, percentScroll, lastScroll,
  221. divS = '<div></div>',
  222. minBarHeight = 30,
  223. releaseScroll = false;
  224. // used in event handlers and for better minification
  225. // var me = $(this);
  226. var me = $(el);
  227. // ensure we are not binding it again
  228. if (me.parent().hasClass(o.wrapperClass)) {
  229. // start from last bar position
  230. var offset = me.scrollTop();
  231. // find bar and rail
  232. bar = me.siblings('.' + o.barClass);
  233. rail = me.siblings('.' + o.railClass);
  234. getBarHeight();
  235. // check if we should scroll existing instance
  236. if ($.isPlainObject(options)) {
  237. // Pass height: auto to an existing slimscroll object to force a resize after contents have changed
  238. if ('height' in options && options.height === 'auto') {
  239. me.parent().css({ height: 'auto' });
  240. me.css({ height: 'auto' });
  241. var height = me.parent().parent().outerHeight();
  242. me.parent().css({ height: height });
  243. me.css({ height: height });
  244. } else if ('height' in options) {
  245. var h = options.height;
  246. me.parent().css({ height: h });
  247. me.css({ height: h });
  248. }
  249. if ('scrollTo' in options) {
  250. // jump to a static point
  251. offset = parseInt(o.scrollTo);
  252. }
  253. else if ('scrollBy' in options) {
  254. // jump by value pixels
  255. offset += parseInt(o.scrollBy);
  256. }
  257. else if ('destroy' in options) {
  258. // remove slimscroll elements
  259. bar.remove();
  260. rail.remove();
  261. me.unwrap();
  262. return;
  263. }
  264. // scroll content by the given offset
  265. scrollContent(offset, false, true);
  266. }
  267. return;
  268. } else if ($.isPlainObject(options)) {
  269. if ('destroy' in options) {
  270. return;
  271. }
  272. }
  273. // optionally set height to the parent's height
  274. o.height = (o.height === 'auto') ? me.parent().outerHeight() : o.height;
  275. // wrap content
  276. var wrapper = $(divS)
  277. .addClass(o.wrapperClass)
  278. .css({
  279. position: 'relative',
  280. overflow: 'hidden',
  281. width: o.width,
  282. height: o.height
  283. });
  284. // update style for the div
  285. me.css({
  286. overflow: 'hidden',
  287. width: o.width,
  288. height: o.height
  289. });
  290. // create scrollbar rail
  291. var rail = $(divS)
  292. .addClass(o.railClass)
  293. .css({
  294. width: o.size,
  295. height: '100%',
  296. position: 'absolute',
  297. top: 0,
  298. display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none',
  299. 'border-radius': o.railBorderRadius,
  300. background: o.railColor,
  301. opacity: o.railOpacity,
  302. zIndex: 998
  303. });
  304. // create scrollbar
  305. var bar = $(divS)
  306. .addClass(o.barClass)
  307. .css({
  308. background: o.color,
  309. width: o.size,
  310. position: 'absolute',
  311. top: 0,
  312. opacity: o.opacity,
  313. display: o.alwaysVisible ? 'block' : 'none',
  314. 'border-radius': o.borderRadius,
  315. BorderRadius: o.borderRadius,
  316. MozBorderRadius: o.borderRadius,
  317. WebkitBorderRadius: o.borderRadius,
  318. zIndex: 999
  319. });
  320. // set position
  321. var posCss = (o.position === 'right') ? { right: o.distance } : { left: o.distance };
  322. rail.css(posCss);
  323. bar.css(posCss);
  324. // wrap it
  325. me.wrap(wrapper);
  326. // append to parent div
  327. me.parent().append(bar);
  328. me.parent().append(rail);
  329. //all binding events callback
  330. var events = {
  331. touchStart: function (e, b) {
  332. if (e.originalEvent.touches.length) {
  333. // record where touch started
  334. touchDif = e.originalEvent.touches[0].pageY;
  335. }
  336. },
  337. touchMove: function (e) {
  338. // prevent scrolling the page if necessary
  339. if (!releaseScroll) {
  340. e.originalEvent.preventDefault();
  341. }
  342. if (e.originalEvent.touches.length) {
  343. // see how far user swiped
  344. var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep;
  345. // scroll content
  346. scrollContent(diff, true);
  347. touchDif = e.originalEvent.touches[0].pageY;
  348. }
  349. },
  350. hoverIn: function () {
  351. isOverPanel = true;
  352. showBar();
  353. hideBar();
  354. },
  355. hoverOut: function () {
  356. isOverPanel = false;
  357. hideBar();
  358. },
  359. barHoverIn: function () {
  360. isOverBar = true;
  361. },
  362. barHoverOut: function () {
  363. isOverBar = false;
  364. },
  365. railHoverIn: function () {
  366. showBar();
  367. },
  368. railHoverOut: function () {
  369. hideBar();
  370. },
  371. barMouseDown: function (e) {
  372. var $doc = $(document);
  373. var t = parseFloat(bar.css('top'));
  374. var pageY = e.pageY;
  375. isDragg = true;
  376. function mousemove(e) {
  377. var currTop = t + e.pageY - pageY;
  378. bar.css({ top: currTop });
  379. scrollContent(0, currTop, false);// scroll content
  380. }
  381. function mouseup(e) {
  382. isDragg = false; hideBar();
  383. $doc.unbind('mousemove', mousemove);
  384. $doc.unbind('mouseup', mouseup);
  385. }
  386. $doc.bind('mousemove', mousemove);
  387. $doc.bind('mouseup', mouseup);
  388. return false;
  389. },
  390. barSelectedStart: function (e) {
  391. e.stopPropagation();
  392. e.preventDefault();
  393. return false;
  394. }
  395. }
  396. // make it draggable and no longer dependent on the jqueryUI
  397. if (o.railDraggable) {
  398. bar.bind('mousedown', events.barMouseDown).bind('selectstart', events.barSelectedStart);
  399. }
  400. // on rail over
  401. rail.hover(events.railHoverIn, events.railHoverOut);
  402. // on bar over
  403. bar.hover(events.barHoverIn, events.barHoverOut);
  404. // show on parent mouseover
  405. me.hover(events.hoverIn, events.hoverOut);
  406. // support for mobile
  407. me.bind('touchstart', events.touchStart);
  408. me.bind('touchmove', events.touchMove);
  409. // set up initial height
  410. getBarHeight();
  411. // check start position
  412. if (o.start === 'bottom') {
  413. // scroll content to bottom
  414. bar.css({ top: me.outerHeight() - bar.outerHeight() });
  415. scrollContent(0, true);
  416. }
  417. else if (o.start !== 'top') {
  418. // assume jQuery selector
  419. scrollContent($(o.start).position().top, null, true);
  420. // make sure bar stays hidden
  421. if (!o.alwaysVisible) { bar.hide(); }
  422. }
  423. // attach scroll events
  424. attachWheel(el);
  425. function _onWheel(e) {
  426. // use mouse wheel only when mouse is over
  427. if (!isOverPanel) { return; }
  428. e = e || window.event;
  429. var delta = 0;
  430. if (e.wheelDelta) { delta = -e.wheelDelta / 120; }
  431. if (e.detail) { delta = e.detail / 3; }
  432. var target = e.target || e.srcTarget || e.srcElement;
  433. if ($(target).closest('.' + o.wrapperClass).is(me.parent())) {
  434. // scroll content
  435. scrollContent(delta, true);
  436. }
  437. // stop window scroll
  438. if (e.preventDefault && !releaseScroll) { e.preventDefault(); }
  439. if (!releaseScroll) { e.returnValue = false; }
  440. }
  441. function scrollContent(y, isWheel, isJump) {
  442. releaseScroll = false;
  443. var delta = y;
  444. var maxTop = me.outerHeight() - bar.outerHeight();
  445. if (isWheel) {
  446. // move bar with mouse wheel
  447. delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight();
  448. // move bar, make sure it doesn't go out
  449. delta = Math.min(Math.max(delta, 0), maxTop);
  450. // if scrolling down, make sure a fractional change to the
  451. // scroll position isn't rounded away when the scrollbar's CSS is set
  452. // this flooring of delta would happened automatically when
  453. // bar.css is set below, but we floor here for clarity
  454. delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);
  455. // scroll the scrollbar
  456. bar.css({ top: delta + 'px' });
  457. }
  458. // calculate actual scroll amount
  459. percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight());
  460. // delta = percentScroll * (me[0].scrollHeight - me.outerHeight());
  461. delta = percentScroll * (me.el.scrollHeight - me.outerHeight());
  462. if (isJump) {
  463. delta = y;
  464. // var offsetTop = delta / me[0].scrollHeight * me.outerHeight();
  465. var offsetTop = delta / me.el.scrollHeight * me.outerHeight();
  466. offsetTop = Math.min(Math.max(offsetTop, 0), maxTop);
  467. bar.css({ top: offsetTop + 'px' });
  468. }
  469. // scroll content
  470. me.scrollTop(delta);
  471. // fire scrolling event
  472. me.trigger('slimscrolling', ~~delta);
  473. // ensure bar is visible
  474. showBar();
  475. // trigger hide when scroll is stopped
  476. hideBar();
  477. }
  478. function attachWheel(target) {
  479. if (window.addEventListener) {
  480. target.addEventListener('DOMMouseScroll', _onWheel, false);
  481. target.addEventListener('mousewheel', _onWheel, false);
  482. }
  483. else {
  484. document.attachEvent('onmousewheel', _onWheel)
  485. }
  486. }
  487. function getBarHeight() {
  488. // calculate scrollbar height and make sure it is not too small
  489. barHeight = Math.max((me.outerHeight() / me.el.scrollHeight) * me.outerHeight(), minBarHeight);
  490. bar.css({ height: barHeight + 'px' });
  491. // hide scrollbar if content is not long enough
  492. var display = barHeight == me.outerHeight() ? 'none' : 'block';
  493. bar.css({ display: display });
  494. }
  495. function showBar() {
  496. // recalculate bar height
  497. getBarHeight();
  498. clearTimeout(queueHide);
  499. // when bar reached top or bottom
  500. if (percentScroll == ~~percentScroll) {
  501. //release wheel
  502. releaseScroll = o.allowPageScroll;
  503. // publish approporiate event
  504. if (lastScroll != percentScroll) {
  505. var msg = (~~percentScroll == 0) ? 'top' : 'bottom';
  506. me.trigger('slimscroll', msg);
  507. }
  508. }
  509. else {
  510. releaseScroll = false;
  511. }
  512. lastScroll = percentScroll;
  513. // show only when required
  514. if (barHeight >= me.outerHeight()) {
  515. //allow window scroll
  516. releaseScroll = true;
  517. return;
  518. }
  519. // bar.stop(true, true).fadeIn('fast');
  520. bar.show()
  521. // if (o.railVisible) { rail.stop(true, true).fadeIn('fast'); }
  522. if (o.railVisible) { rail.show(); }
  523. }
  524. function hideBar() {
  525. // only hide when options allow it
  526. if (!o.alwaysVisible) {
  527. queueHide = setTimeout(function () {
  528. if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) {
  529. // bar.fadeOut('slow');
  530. // rail.fadeOut('slow');
  531. bar.hide()
  532. rail.hide()
  533. }
  534. }, 1000);
  535. }
  536. }
  537. // });
  538. function unbind() {
  539. // make it draggable and no longer dependent on the jqueryUI
  540. bar.unbind('mousedown', events.barMouseDown).unbind('selectstart', events.barSelectedStart);
  541. // on rail over
  542. rail.unbind('mouseenter', events.railHoverIn).unbind('mouseleave', events.railHoverOut);
  543. // on bar over
  544. bar.unbind('mouseenter', events.barHoverIn).unbind('mouseleave', events.barHoverOut);
  545. // show on parent mouseover
  546. me.unbind('mouseenter', events.hoverIn).unbind('mouseleave', events.hoverOut);
  547. // support for mobile
  548. me.unbind('touchstart', events.touchStart);
  549. me.unbind('touchmove', events.touchMove);
  550. }
  551. return {
  552. unbind: function () {
  553. bar.unbind('mousedown', events.barMouseDown)
  554. .unbind('mouseenter', events.barHoverIn)
  555. .unbind('mouseleave', events.barHoverOut)
  556. .unbind('selectstart', events.barSelectedStart);
  557. rail.unbind('mouseenter', events.railHoverIn)
  558. .unbind('mouseleave', events.railHoverOut);
  559. bar.unbind('mouseenter', events.barHoverIn)
  560. .unbind('mouseleave', events.barHoverOut);
  561. me.unbind('mouseenter', events.hoverIn)
  562. .unbind('mouseleave', events.hoverOut)
  563. .unbind('touchstart', events.touchStart)
  564. .unbind('touchmove', events.touchMove);
  565. }
  566. }
  567. }
  568. return SlimScroll
  569. })()
  570. var VueSlimScroll = {}
  571. VueSlimScroll.install = function (Vue) {
  572. var ss
  573. Vue.directive('slimscroll', {
  574. inserted: function (el, binding) {
  575. ss = SlimScroll(el, binding.value)
  576. },
  577. unbind: function () {
  578. ss.unbind()
  579. }
  580. })
  581. }
  582. if (typeof exports == "object") {
  583. module.exports = VueSlimScroll
  584. } else if (window.Vue) {
  585. window.VueSlimScroll = VueSlimScroll
  586. Vue.use(VueSlimScroll)
  587. }