perfect-scrollbar.js (35321B)
1 /*! 2 * perfect-scrollbar v1.4.0 3 * (c) 2018 Hyunje Jun 4 * @license MIT 5 */ 6 (function (global, factory) { 7 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 typeof define === 'function' && define.amd ? define(factory) : 9 (global.PerfectScrollbar = factory()); 10 }(this, (function () { 'use strict'; 11 12 function get(element) { 13 return getComputedStyle(element); 14 } 15 16 function set(element, obj) { 17 for (var key in obj) { 18 var val = obj[key]; 19 if (typeof val === 'number') { 20 val = val + "px"; 21 } 22 element.style[key] = val; 23 } 24 return element; 25 } 26 27 function div(className) { 28 var div = document.createElement('div'); 29 div.className = className; 30 return div; 31 } 32 33 var elMatches = 34 typeof Element !== 'undefined' && 35 (Element.prototype.matches || 36 Element.prototype.webkitMatchesSelector || 37 Element.prototype.mozMatchesSelector || 38 Element.prototype.msMatchesSelector); 39 40 function matches(element, query) { 41 if (!elMatches) { 42 throw new Error('No element matching method supported'); 43 } 44 45 return elMatches.call(element, query); 46 } 47 48 function remove(element) { 49 if (element.remove) { 50 element.remove(); 51 } else { 52 if (element.parentNode) { 53 element.parentNode.removeChild(element); 54 } 55 } 56 } 57 58 function queryChildren(element, selector) { 59 return Array.prototype.filter.call(element.children, function (child) { return matches(child, selector); } 60 ); 61 } 62 63 var cls = { 64 main: 'ps', 65 element: { 66 thumb: function (x) { return ("ps__thumb-" + x); }, 67 rail: function (x) { return ("ps__rail-" + x); }, 68 consuming: 'ps__child--consume', 69 }, 70 state: { 71 focus: 'ps--focus', 72 clicking: 'ps--clicking', 73 active: function (x) { return ("ps--active-" + x); }, 74 scrolling: function (x) { return ("ps--scrolling-" + x); }, 75 }, 76 }; 77 78 /* 79 * Helper methods 80 */ 81 var scrollingClassTimeout = { x: null, y: null }; 82 83 function addScrollingClass(i, x) { 84 var classList = i.element.classList; 85 var className = cls.state.scrolling(x); 86 87 if (classList.contains(className)) { 88 clearTimeout(scrollingClassTimeout[x]); 89 } else { 90 classList.add(className); 91 } 92 } 93 94 function removeScrollingClass(i, x) { 95 scrollingClassTimeout[x] = setTimeout( 96 function () { return i.isAlive && i.element.classList.remove(cls.state.scrolling(x)); }, 97 i.settings.scrollingThreshold 98 ); 99 } 100 101 function setScrollingClassInstantly(i, x) { 102 addScrollingClass(i, x); 103 removeScrollingClass(i, x); 104 } 105 106 var EventElement = function EventElement(element) { 107 this.element = element; 108 this.handlers = {}; 109 }; 110 111 var prototypeAccessors = { isEmpty: { configurable: true } }; 112 113 EventElement.prototype.bind = function bind (eventName, handler) { 114 if (typeof this.handlers[eventName] === 'undefined') { 115 this.handlers[eventName] = []; 116 } 117 this.handlers[eventName].push(handler); 118 this.element.addEventListener(eventName, handler, false); 119 }; 120 121 EventElement.prototype.unbind = function unbind (eventName, target) { 122 var this$1 = this; 123 124 this.handlers[eventName] = this.handlers[eventName].filter(function (handler) { 125 if (target && handler !== target) { 126 return true; 127 } 128 this$1.element.removeEventListener(eventName, handler, false); 129 return false; 130 }); 131 }; 132 133 EventElement.prototype.unbindAll = function unbindAll () { 134 var this$1 = this; 135 136 for (var name in this$1.handlers) { 137 this$1.unbind(name); 138 } 139 }; 140 141 prototypeAccessors.isEmpty.get = function () { 142 var this$1 = this; 143 144 return Object.keys(this.handlers).every( 145 function (key) { return this$1.handlers[key].length === 0; } 146 ); 147 }; 148 149 Object.defineProperties( EventElement.prototype, prototypeAccessors ); 150 151 var EventManager = function EventManager() { 152 this.eventElements = []; 153 }; 154 155 EventManager.prototype.eventElement = function eventElement (element) { 156 var ee = this.eventElements.filter(function (ee) { return ee.element === element; })[0]; 157 if (!ee) { 158 ee = new EventElement(element); 159 this.eventElements.push(ee); 160 } 161 return ee; 162 }; 163 164 EventManager.prototype.bind = function bind (element, eventName, handler) { 165 this.eventElement(element).bind(eventName, handler); 166 }; 167 168 EventManager.prototype.unbind = function unbind (element, eventName, handler) { 169 var ee = this.eventElement(element); 170 ee.unbind(eventName, handler); 171 172 if (ee.isEmpty) { 173 // remove 174 this.eventElements.splice(this.eventElements.indexOf(ee), 1); 175 } 176 }; 177 178 EventManager.prototype.unbindAll = function unbindAll () { 179 this.eventElements.forEach(function (e) { return e.unbindAll(); }); 180 this.eventElements = []; 181 }; 182 183 EventManager.prototype.once = function once (element, eventName, handler) { 184 var ee = this.eventElement(element); 185 var onceHandler = function (evt) { 186 ee.unbind(eventName, onceHandler); 187 handler(evt); 188 }; 189 ee.bind(eventName, onceHandler); 190 }; 191 192 function createEvent(name) { 193 if (typeof window.CustomEvent === 'function') { 194 return new CustomEvent(name); 195 } else { 196 var evt = document.createEvent('CustomEvent'); 197 evt.initCustomEvent(name, false, false, undefined); 198 return evt; 199 } 200 } 201 202 var processScrollDiff = function( 203 i, 204 axis, 205 diff, 206 useScrollingClass, 207 forceFireReachEvent 208 ) { 209 if ( useScrollingClass === void 0 ) useScrollingClass = true; 210 if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false; 211 212 var fields; 213 if (axis === 'top') { 214 fields = [ 215 'contentHeight', 216 'containerHeight', 217 'scrollTop', 218 'y', 219 'up', 220 'down' ]; 221 } else if (axis === 'left') { 222 fields = [ 223 'contentWidth', 224 'containerWidth', 225 'scrollLeft', 226 'x', 227 'left', 228 'right' ]; 229 } else { 230 throw new Error('A proper axis should be provided'); 231 } 232 233 processScrollDiff$1(i, diff, fields, useScrollingClass, forceFireReachEvent); 234 }; 235 236 function processScrollDiff$1( 237 i, 238 diff, 239 ref, 240 useScrollingClass, 241 forceFireReachEvent 242 ) { 243 var contentHeight = ref[0]; 244 var containerHeight = ref[1]; 245 var scrollTop = ref[2]; 246 var y = ref[3]; 247 var up = ref[4]; 248 var down = ref[5]; 249 if ( useScrollingClass === void 0 ) useScrollingClass = true; 250 if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false; 251 252 var element = i.element; 253 254 // reset reach 255 i.reach[y] = null; 256 257 // 1 for subpixel rounding 258 if (element[scrollTop] < 1) { 259 i.reach[y] = 'start'; 260 } 261 262 // 1 for subpixel rounding 263 if (element[scrollTop] > i[contentHeight] - i[containerHeight] - 1) { 264 i.reach[y] = 'end'; 265 } 266 267 if (diff) { 268 element.dispatchEvent(createEvent(("ps-scroll-" + y))); 269 270 if (diff < 0) { 271 element.dispatchEvent(createEvent(("ps-scroll-" + up))); 272 } else if (diff > 0) { 273 element.dispatchEvent(createEvent(("ps-scroll-" + down))); 274 } 275 276 if (useScrollingClass) { 277 setScrollingClassInstantly(i, y); 278 } 279 } 280 281 if (i.reach[y] && (diff || forceFireReachEvent)) { 282 element.dispatchEvent(createEvent(("ps-" + y + "-reach-" + (i.reach[y])))); 283 } 284 } 285 286 function toInt(x) { 287 return parseInt(x, 10) || 0; 288 } 289 290 function isEditable(el) { 291 return ( 292 matches(el, 'input,[contenteditable]') || 293 matches(el, 'select,[contenteditable]') || 294 matches(el, 'textarea,[contenteditable]') || 295 matches(el, 'button,[contenteditable]') 296 ); 297 } 298 299 function outerWidth(element) { 300 var styles = get(element); 301 return ( 302 toInt(styles.width) + 303 toInt(styles.paddingLeft) + 304 toInt(styles.paddingRight) + 305 toInt(styles.borderLeftWidth) + 306 toInt(styles.borderRightWidth) 307 ); 308 } 309 310 var env = { 311 isWebKit: 312 typeof document !== 'undefined' && 313 'WebkitAppearance' in document.documentElement.style, 314 supportsTouch: 315 typeof window !== 'undefined' && 316 ('ontouchstart' in window || 317 (window.DocumentTouch && document instanceof window.DocumentTouch)), 318 supportsIePointer: 319 typeof navigator !== 'undefined' && navigator.msMaxTouchPoints, 320 isChrome: 321 typeof navigator !== 'undefined' && 322 /Chrome/i.test(navigator && navigator.userAgent), 323 }; 324 325 var updateGeometry = function(i) { 326 var element = i.element; 327 var roundedScrollTop = Math.floor(element.scrollTop); 328 329 i.containerWidth = element.clientWidth; 330 i.containerHeight = element.clientHeight; 331 i.contentWidth = element.scrollWidth; 332 i.contentHeight = element.scrollHeight; 333 334 if (!element.contains(i.scrollbarXRail)) { 335 // clean up and append 336 queryChildren(element, cls.element.rail('x')).forEach(function (el) { return remove(el); } 337 ); 338 element.appendChild(i.scrollbarXRail); 339 } 340 if (!element.contains(i.scrollbarYRail)) { 341 // clean up and append 342 queryChildren(element, cls.element.rail('y')).forEach(function (el) { return remove(el); } 343 ); 344 element.appendChild(i.scrollbarYRail); 345 } 346 347 if ( 348 !i.settings.suppressScrollX && 349 i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth 350 ) { 351 i.scrollbarXActive = true; 352 i.railXWidth = i.containerWidth - i.railXMarginWidth; 353 i.railXRatio = i.containerWidth / i.railXWidth; 354 i.scrollbarXWidth = getThumbSize( 355 i, 356 toInt(i.railXWidth * i.containerWidth / i.contentWidth) 357 ); 358 i.scrollbarXLeft = toInt( 359 (i.negativeScrollAdjustment + element.scrollLeft) * 360 (i.railXWidth - i.scrollbarXWidth) / 361 (i.contentWidth - i.containerWidth) 362 ); 363 } else { 364 i.scrollbarXActive = false; 365 } 366 367 if ( 368 !i.settings.suppressScrollY && 369 i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight 370 ) { 371 i.scrollbarYActive = true; 372 i.railYHeight = i.containerHeight - i.railYMarginHeight; 373 i.railYRatio = i.containerHeight / i.railYHeight; 374 i.scrollbarYHeight = getThumbSize( 375 i, 376 toInt(i.railYHeight * i.containerHeight / i.contentHeight) 377 ); 378 i.scrollbarYTop = toInt( 379 roundedScrollTop * 380 (i.railYHeight - i.scrollbarYHeight) / 381 (i.contentHeight - i.containerHeight) 382 ); 383 } else { 384 i.scrollbarYActive = false; 385 } 386 387 if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) { 388 i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth; 389 } 390 if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) { 391 i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight; 392 } 393 394 updateCss(element, i); 395 396 if (i.scrollbarXActive) { 397 element.classList.add(cls.state.active('x')); 398 } else { 399 element.classList.remove(cls.state.active('x')); 400 i.scrollbarXWidth = 0; 401 i.scrollbarXLeft = 0; 402 element.scrollLeft = 0; 403 } 404 if (i.scrollbarYActive) { 405 element.classList.add(cls.state.active('y')); 406 } else { 407 element.classList.remove(cls.state.active('y')); 408 i.scrollbarYHeight = 0; 409 i.scrollbarYTop = 0; 410 element.scrollTop = 0; 411 } 412 }; 413 414 function getThumbSize(i, thumbSize) { 415 if (i.settings.minScrollbarLength) { 416 thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength); 417 } 418 if (i.settings.maxScrollbarLength) { 419 thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength); 420 } 421 return thumbSize; 422 } 423 424 function updateCss(element, i) { 425 var xRailOffset = { width: i.railXWidth }; 426 var roundedScrollTop = Math.floor(element.scrollTop); 427 428 if (i.isRtl) { 429 xRailOffset.left = 430 i.negativeScrollAdjustment + 431 element.scrollLeft + 432 i.containerWidth - 433 i.contentWidth; 434 } else { 435 xRailOffset.left = element.scrollLeft; 436 } 437 if (i.isScrollbarXUsingBottom) { 438 xRailOffset.bottom = i.scrollbarXBottom - roundedScrollTop; 439 } else { 440 xRailOffset.top = i.scrollbarXTop + roundedScrollTop; 441 } 442 set(i.scrollbarXRail, xRailOffset); 443 444 var yRailOffset = { top: roundedScrollTop, height: i.railYHeight }; 445 if (i.isScrollbarYUsingRight) { 446 if (i.isRtl) { 447 yRailOffset.right = 448 i.contentWidth - 449 (i.negativeScrollAdjustment + element.scrollLeft) - 450 i.scrollbarYRight - 451 i.scrollbarYOuterWidth; 452 } else { 453 yRailOffset.right = i.scrollbarYRight - element.scrollLeft; 454 } 455 } else { 456 if (i.isRtl) { 457 yRailOffset.left = 458 i.negativeScrollAdjustment + 459 element.scrollLeft + 460 i.containerWidth * 2 - 461 i.contentWidth - 462 i.scrollbarYLeft - 463 i.scrollbarYOuterWidth; 464 } else { 465 yRailOffset.left = i.scrollbarYLeft + element.scrollLeft; 466 } 467 } 468 set(i.scrollbarYRail, yRailOffset); 469 470 set(i.scrollbarX, { 471 left: i.scrollbarXLeft, 472 width: i.scrollbarXWidth - i.railBorderXWidth, 473 }); 474 set(i.scrollbarY, { 475 top: i.scrollbarYTop, 476 height: i.scrollbarYHeight - i.railBorderYWidth, 477 }); 478 } 479 480 var clickRail = function(i) { 481 i.event.bind(i.scrollbarY, 'mousedown', function (e) { return e.stopPropagation(); }); 482 i.event.bind(i.scrollbarYRail, 'mousedown', function (e) { 483 var positionTop = 484 e.pageY - 485 window.pageYOffset - 486 i.scrollbarYRail.getBoundingClientRect().top; 487 var direction = positionTop > i.scrollbarYTop ? 1 : -1; 488 489 i.element.scrollTop += direction * i.containerHeight; 490 updateGeometry(i); 491 492 e.stopPropagation(); 493 }); 494 495 i.event.bind(i.scrollbarX, 'mousedown', function (e) { return e.stopPropagation(); }); 496 i.event.bind(i.scrollbarXRail, 'mousedown', function (e) { 497 var positionLeft = 498 e.pageX - 499 window.pageXOffset - 500 i.scrollbarXRail.getBoundingClientRect().left; 501 var direction = positionLeft > i.scrollbarXLeft ? 1 : -1; 502 503 i.element.scrollLeft += direction * i.containerWidth; 504 updateGeometry(i); 505 506 e.stopPropagation(); 507 }); 508 }; 509 510 var dragThumb = function(i) { 511 bindMouseScrollHandler(i, [ 512 'containerWidth', 513 'contentWidth', 514 'pageX', 515 'railXWidth', 516 'scrollbarX', 517 'scrollbarXWidth', 518 'scrollLeft', 519 'x', 520 'scrollbarXRail' ]); 521 bindMouseScrollHandler(i, [ 522 'containerHeight', 523 'contentHeight', 524 'pageY', 525 'railYHeight', 526 'scrollbarY', 527 'scrollbarYHeight', 528 'scrollTop', 529 'y', 530 'scrollbarYRail' ]); 531 }; 532 533 function bindMouseScrollHandler( 534 i, 535 ref 536 ) { 537 var containerHeight = ref[0]; 538 var contentHeight = ref[1]; 539 var pageY = ref[2]; 540 var railYHeight = ref[3]; 541 var scrollbarY = ref[4]; 542 var scrollbarYHeight = ref[5]; 543 var scrollTop = ref[6]; 544 var y = ref[7]; 545 var scrollbarYRail = ref[8]; 546 547 var element = i.element; 548 549 var startingScrollTop = null; 550 var startingMousePageY = null; 551 var scrollBy = null; 552 553 function mouseMoveHandler(e) { 554 element[scrollTop] = 555 startingScrollTop + scrollBy * (e[pageY] - startingMousePageY); 556 addScrollingClass(i, y); 557 updateGeometry(i); 558 559 e.stopPropagation(); 560 e.preventDefault(); 561 } 562 563 function mouseUpHandler() { 564 removeScrollingClass(i, y); 565 i[scrollbarYRail].classList.remove(cls.state.clicking); 566 i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler); 567 } 568 569 i.event.bind(i[scrollbarY], 'mousedown', function (e) { 570 startingScrollTop = element[scrollTop]; 571 startingMousePageY = e[pageY]; 572 scrollBy = 573 (i[contentHeight] - i[containerHeight]) / 574 (i[railYHeight] - i[scrollbarYHeight]); 575 576 i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler); 577 i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler); 578 579 i[scrollbarYRail].classList.add(cls.state.clicking); 580 581 e.stopPropagation(); 582 e.preventDefault(); 583 }); 584 } 585 586 var keyboard = function(i) { 587 var element = i.element; 588 589 var elementHovered = function () { return matches(element, ':hover'); }; 590 var scrollbarFocused = function () { return matches(i.scrollbarX, ':focus') || matches(i.scrollbarY, ':focus'); }; 591 592 function shouldPreventDefault(deltaX, deltaY) { 593 var scrollTop = Math.floor(element.scrollTop); 594 if (deltaX === 0) { 595 if (!i.scrollbarYActive) { 596 return false; 597 } 598 if ( 599 (scrollTop === 0 && deltaY > 0) || 600 (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0) 601 ) { 602 return !i.settings.wheelPropagation; 603 } 604 } 605 606 var scrollLeft = element.scrollLeft; 607 if (deltaY === 0) { 608 if (!i.scrollbarXActive) { 609 return false; 610 } 611 if ( 612 (scrollLeft === 0 && deltaX < 0) || 613 (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0) 614 ) { 615 return !i.settings.wheelPropagation; 616 } 617 } 618 return true; 619 } 620 621 i.event.bind(i.ownerDocument, 'keydown', function (e) { 622 if ( 623 (e.isDefaultPrevented && e.isDefaultPrevented()) || 624 e.defaultPrevented 625 ) { 626 return; 627 } 628 629 if (!elementHovered() && !scrollbarFocused()) { 630 return; 631 } 632 633 var activeElement = document.activeElement 634 ? document.activeElement 635 : i.ownerDocument.activeElement; 636 if (activeElement) { 637 if (activeElement.tagName === 'IFRAME') { 638 activeElement = activeElement.contentDocument.activeElement; 639 } else { 640 // go deeper if element is a webcomponent 641 while (activeElement.shadowRoot) { 642 activeElement = activeElement.shadowRoot.activeElement; 643 } 644 } 645 if (isEditable(activeElement)) { 646 return; 647 } 648 } 649 650 var deltaX = 0; 651 var deltaY = 0; 652 653 switch (e.which) { 654 case 37: // left 655 if (e.metaKey) { 656 deltaX = -i.contentWidth; 657 } else if (e.altKey) { 658 deltaX = -i.containerWidth; 659 } else { 660 deltaX = -30; 661 } 662 break; 663 case 38: // up 664 if (e.metaKey) { 665 deltaY = i.contentHeight; 666 } else if (e.altKey) { 667 deltaY = i.containerHeight; 668 } else { 669 deltaY = 30; 670 } 671 break; 672 case 39: // right 673 if (e.metaKey) { 674 deltaX = i.contentWidth; 675 } else if (e.altKey) { 676 deltaX = i.containerWidth; 677 } else { 678 deltaX = 30; 679 } 680 break; 681 case 40: // down 682 if (e.metaKey) { 683 deltaY = -i.contentHeight; 684 } else if (e.altKey) { 685 deltaY = -i.containerHeight; 686 } else { 687 deltaY = -30; 688 } 689 break; 690 case 32: // space bar 691 if (e.shiftKey) { 692 deltaY = i.containerHeight; 693 } else { 694 deltaY = -i.containerHeight; 695 } 696 break; 697 case 33: // page up 698 deltaY = i.containerHeight; 699 break; 700 case 34: // page down 701 deltaY = -i.containerHeight; 702 break; 703 case 36: // home 704 deltaY = i.contentHeight; 705 break; 706 case 35: // end 707 deltaY = -i.contentHeight; 708 break; 709 default: 710 return; 711 } 712 713 if (i.settings.suppressScrollX && deltaX !== 0) { 714 return; 715 } 716 if (i.settings.suppressScrollY && deltaY !== 0) { 717 return; 718 } 719 720 element.scrollTop -= deltaY; 721 element.scrollLeft += deltaX; 722 updateGeometry(i); 723 724 if (shouldPreventDefault(deltaX, deltaY)) { 725 e.preventDefault(); 726 } 727 }); 728 }; 729 730 var wheel = function(i) { 731 var element = i.element; 732 733 function shouldPreventDefault(deltaX, deltaY) { 734 var roundedScrollTop = Math.floor(element.scrollTop); 735 var isTop = element.scrollTop === 0; 736 var isBottom = 737 roundedScrollTop + element.offsetHeight === element.scrollHeight; 738 var isLeft = element.scrollLeft === 0; 739 var isRight = 740 element.scrollLeft + element.offsetWidth === element.scrollWidth; 741 742 var hitsBound; 743 744 // pick axis with primary direction 745 if (Math.abs(deltaY) > Math.abs(deltaX)) { 746 hitsBound = isTop || isBottom; 747 } else { 748 hitsBound = isLeft || isRight; 749 } 750 751 return hitsBound ? !i.settings.wheelPropagation : true; 752 } 753 754 function getDeltaFromEvent(e) { 755 var deltaX = e.deltaX; 756 var deltaY = -1 * e.deltaY; 757 758 if (typeof deltaX === 'undefined' || typeof deltaY === 'undefined') { 759 // OS X Safari 760 deltaX = -1 * e.wheelDeltaX / 6; 761 deltaY = e.wheelDeltaY / 6; 762 } 763 764 if (e.deltaMode && e.deltaMode === 1) { 765 // Firefox in deltaMode 1: Line scrolling 766 deltaX *= 10; 767 deltaY *= 10; 768 } 769 770 if (deltaX !== deltaX && deltaY !== deltaY /* NaN checks */) { 771 // IE in some mouse drivers 772 deltaX = 0; 773 deltaY = e.wheelDelta; 774 } 775 776 if (e.shiftKey) { 777 // reverse axis with shift key 778 return [-deltaY, -deltaX]; 779 } 780 return [deltaX, deltaY]; 781 } 782 783 function shouldBeConsumedByChild(target, deltaX, deltaY) { 784 // FIXME: this is a workaround for <select> issue in FF and IE #571 785 if (!env.isWebKit && element.querySelector('select:focus')) { 786 return true; 787 } 788 789 if (!element.contains(target)) { 790 return false; 791 } 792 793 var cursor = target; 794 795 while (cursor && cursor !== element) { 796 if (cursor.classList.contains(cls.element.consuming)) { 797 return true; 798 } 799 800 var style = get(cursor); 801 var overflow = [style.overflow, style.overflowX, style.overflowY].join( 802 '' 803 ); 804 805 // if scrollable 806 if (overflow.match(/(scroll|auto)/)) { 807 var maxScrollTop = cursor.scrollHeight - cursor.clientHeight; 808 if (maxScrollTop > 0) { 809 if ( 810 !(cursor.scrollTop === 0 && deltaY > 0) && 811 !(cursor.scrollTop === maxScrollTop && deltaY < 0) 812 ) { 813 return true; 814 } 815 } 816 var maxScrollLeft = cursor.scrollWidth - cursor.clientWidth; 817 if (maxScrollLeft > 0) { 818 if ( 819 !(cursor.scrollLeft === 0 && deltaX < 0) && 820 !(cursor.scrollLeft === maxScrollLeft && deltaX > 0) 821 ) { 822 return true; 823 } 824 } 825 } 826 827 cursor = cursor.parentNode; 828 } 829 830 return false; 831 } 832 833 function mousewheelHandler(e) { 834 var ref = getDeltaFromEvent(e); 835 var deltaX = ref[0]; 836 var deltaY = ref[1]; 837 838 if (shouldBeConsumedByChild(e.target, deltaX, deltaY)) { 839 return; 840 } 841 842 var shouldPrevent = false; 843 if (!i.settings.useBothWheelAxes) { 844 // deltaX will only be used for horizontal scrolling and deltaY will 845 // only be used for vertical scrolling - this is the default 846 element.scrollTop -= deltaY * i.settings.wheelSpeed; 847 element.scrollLeft += deltaX * i.settings.wheelSpeed; 848 } else if (i.scrollbarYActive && !i.scrollbarXActive) { 849 // only vertical scrollbar is active and useBothWheelAxes option is 850 // active, so let's scroll vertical bar using both mouse wheel axes 851 if (deltaY) { 852 element.scrollTop -= deltaY * i.settings.wheelSpeed; 853 } else { 854 element.scrollTop += deltaX * i.settings.wheelSpeed; 855 } 856 shouldPrevent = true; 857 } else if (i.scrollbarXActive && !i.scrollbarYActive) { 858 // useBothWheelAxes and only horizontal bar is active, so use both 859 // wheel axes for horizontal bar 860 if (deltaX) { 861 element.scrollLeft += deltaX * i.settings.wheelSpeed; 862 } else { 863 element.scrollLeft -= deltaY * i.settings.wheelSpeed; 864 } 865 shouldPrevent = true; 866 } 867 868 updateGeometry(i); 869 870 shouldPrevent = shouldPrevent || shouldPreventDefault(deltaX, deltaY); 871 if (shouldPrevent && !e.ctrlKey) { 872 e.stopPropagation(); 873 e.preventDefault(); 874 } 875 } 876 877 if (typeof window.onwheel !== 'undefined') { 878 i.event.bind(element, 'wheel', mousewheelHandler); 879 } else if (typeof window.onmousewheel !== 'undefined') { 880 i.event.bind(element, 'mousewheel', mousewheelHandler); 881 } 882 }; 883 884 var touch = function(i) { 885 if (!env.supportsTouch && !env.supportsIePointer) { 886 return; 887 } 888 889 var element = i.element; 890 891 function shouldPrevent(deltaX, deltaY) { 892 var scrollTop = Math.floor(element.scrollTop); 893 var scrollLeft = element.scrollLeft; 894 var magnitudeX = Math.abs(deltaX); 895 var magnitudeY = Math.abs(deltaY); 896 897 if (magnitudeY > magnitudeX) { 898 // user is perhaps trying to swipe up/down the page 899 900 if ( 901 (deltaY < 0 && scrollTop === i.contentHeight - i.containerHeight) || 902 (deltaY > 0 && scrollTop === 0) 903 ) { 904 // set prevent for mobile Chrome refresh 905 return window.scrollY === 0 && deltaY > 0 && env.isChrome; 906 } 907 } else if (magnitudeX > magnitudeY) { 908 // user is perhaps trying to swipe left/right across the page 909 910 if ( 911 (deltaX < 0 && scrollLeft === i.contentWidth - i.containerWidth) || 912 (deltaX > 0 && scrollLeft === 0) 913 ) { 914 return true; 915 } 916 } 917 918 return true; 919 } 920 921 function applyTouchMove(differenceX, differenceY) { 922 element.scrollTop -= differenceY; 923 element.scrollLeft -= differenceX; 924 925 updateGeometry(i); 926 } 927 928 var startOffset = {}; 929 var startTime = 0; 930 var speed = {}; 931 var easingLoop = null; 932 933 function getTouch(e) { 934 if (e.targetTouches) { 935 return e.targetTouches[0]; 936 } else { 937 // Maybe IE pointer 938 return e; 939 } 940 } 941 942 function shouldHandle(e) { 943 if (e.pointerType && e.pointerType === 'pen' && e.buttons === 0) { 944 return false; 945 } 946 if (e.targetTouches && e.targetTouches.length === 1) { 947 return true; 948 } 949 if ( 950 e.pointerType && 951 e.pointerType !== 'mouse' && 952 e.pointerType !== e.MSPOINTER_TYPE_MOUSE 953 ) { 954 return true; 955 } 956 return false; 957 } 958 959 function touchStart(e) { 960 if (!shouldHandle(e)) { 961 return; 962 } 963 964 var touch = getTouch(e); 965 966 startOffset.pageX = touch.pageX; 967 startOffset.pageY = touch.pageY; 968 969 startTime = new Date().getTime(); 970 971 if (easingLoop !== null) { 972 clearInterval(easingLoop); 973 } 974 } 975 976 function shouldBeConsumedByChild(target, deltaX, deltaY) { 977 if (!element.contains(target)) { 978 return false; 979 } 980 981 var cursor = target; 982 983 while (cursor && cursor !== element) { 984 if (cursor.classList.contains(cls.element.consuming)) { 985 return true; 986 } 987 988 var style = get(cursor); 989 var overflow = [style.overflow, style.overflowX, style.overflowY].join( 990 '' 991 ); 992 993 // if scrollable 994 if (overflow.match(/(scroll|auto)/)) { 995 var maxScrollTop = cursor.scrollHeight - cursor.clientHeight; 996 if (maxScrollTop > 0) { 997 if ( 998 !(cursor.scrollTop === 0 && deltaY > 0) && 999 !(cursor.scrollTop === maxScrollTop && deltaY < 0) 1000 ) { 1001 return true; 1002 } 1003 } 1004 var maxScrollLeft = cursor.scrollLeft - cursor.clientWidth; 1005 if (maxScrollLeft > 0) { 1006 if ( 1007 !(cursor.scrollLeft === 0 && deltaX < 0) && 1008 !(cursor.scrollLeft === maxScrollLeft && deltaX > 0) 1009 ) { 1010 return true; 1011 } 1012 } 1013 } 1014 1015 cursor = cursor.parentNode; 1016 } 1017 1018 return false; 1019 } 1020 1021 function touchMove(e) { 1022 if (shouldHandle(e)) { 1023 var touch = getTouch(e); 1024 1025 var currentOffset = { pageX: touch.pageX, pageY: touch.pageY }; 1026 1027 var differenceX = currentOffset.pageX - startOffset.pageX; 1028 var differenceY = currentOffset.pageY - startOffset.pageY; 1029 1030 if (shouldBeConsumedByChild(e.target, differenceX, differenceY)) { 1031 return; 1032 } 1033 1034 applyTouchMove(differenceX, differenceY); 1035 startOffset = currentOffset; 1036 1037 var currentTime = new Date().getTime(); 1038 1039 var timeGap = currentTime - startTime; 1040 if (timeGap > 0) { 1041 speed.x = differenceX / timeGap; 1042 speed.y = differenceY / timeGap; 1043 startTime = currentTime; 1044 } 1045 1046 if (shouldPrevent(differenceX, differenceY)) { 1047 e.preventDefault(); 1048 } 1049 } 1050 } 1051 function touchEnd() { 1052 if (i.settings.swipeEasing) { 1053 clearInterval(easingLoop); 1054 easingLoop = setInterval(function() { 1055 if (i.isInitialized) { 1056 clearInterval(easingLoop); 1057 return; 1058 } 1059 1060 if (!speed.x && !speed.y) { 1061 clearInterval(easingLoop); 1062 return; 1063 } 1064 1065 if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { 1066 clearInterval(easingLoop); 1067 return; 1068 } 1069 1070 applyTouchMove(speed.x * 30, speed.y * 30); 1071 1072 speed.x *= 0.8; 1073 speed.y *= 0.8; 1074 }, 10); 1075 } 1076 } 1077 1078 if (env.supportsTouch) { 1079 i.event.bind(element, 'touchstart', touchStart); 1080 i.event.bind(element, 'touchmove', touchMove); 1081 i.event.bind(element, 'touchend', touchEnd); 1082 } else if (env.supportsIePointer) { 1083 if (window.PointerEvent) { 1084 i.event.bind(element, 'pointerdown', touchStart); 1085 i.event.bind(element, 'pointermove', touchMove); 1086 i.event.bind(element, 'pointerup', touchEnd); 1087 } else if (window.MSPointerEvent) { 1088 i.event.bind(element, 'MSPointerDown', touchStart); 1089 i.event.bind(element, 'MSPointerMove', touchMove); 1090 i.event.bind(element, 'MSPointerUp', touchEnd); 1091 } 1092 } 1093 }; 1094 1095 var defaultSettings = function () { return ({ 1096 handlers: ['click-rail', 'drag-thumb', 'keyboard', 'wheel', 'touch'], 1097 maxScrollbarLength: null, 1098 minScrollbarLength: null, 1099 scrollingThreshold: 1000, 1100 scrollXMarginOffset: 0, 1101 scrollYMarginOffset: 0, 1102 suppressScrollX: false, 1103 suppressScrollY: false, 1104 swipeEasing: true, 1105 useBothWheelAxes: false, 1106 wheelPropagation: true, 1107 wheelSpeed: 1, 1108 }); }; 1109 1110 var handlers = { 1111 'click-rail': clickRail, 1112 'drag-thumb': dragThumb, 1113 keyboard: keyboard, 1114 wheel: wheel, 1115 touch: touch, 1116 }; 1117 1118 var PerfectScrollbar = function PerfectScrollbar(element, userSettings) { 1119 var this$1 = this; 1120 if ( userSettings === void 0 ) userSettings = {}; 1121 1122 if (typeof element === 'string') { 1123 element = document.querySelector(element); 1124 } 1125 1126 if (!element || !element.nodeName) { 1127 throw new Error('no element is specified to initialize PerfectScrollbar'); 1128 } 1129 1130 this.element = element; 1131 1132 element.classList.add(cls.main); 1133 1134 this.settings = defaultSettings(); 1135 for (var key in userSettings) { 1136 this$1.settings[key] = userSettings[key]; 1137 } 1138 1139 this.containerWidth = null; 1140 this.containerHeight = null; 1141 this.contentWidth = null; 1142 this.contentHeight = null; 1143 1144 var focus = function () { return element.classList.add(cls.state.focus); }; 1145 var blur = function () { return element.classList.remove(cls.state.focus); }; 1146 1147 this.isRtl = get(element).direction === 'rtl'; 1148 this.isNegativeScroll = (function () { 1149 var originalScrollLeft = element.scrollLeft; 1150 var result = null; 1151 element.scrollLeft = -1; 1152 result = element.scrollLeft < 0; 1153 element.scrollLeft = originalScrollLeft; 1154 return result; 1155 })(); 1156 this.negativeScrollAdjustment = this.isNegativeScroll 1157 ? element.scrollWidth - element.clientWidth 1158 : 0; 1159 this.event = new EventManager(); 1160 this.ownerDocument = element.ownerDocument || document; 1161 1162 this.scrollbarXRail = div(cls.element.rail('x')); 1163 element.appendChild(this.scrollbarXRail); 1164 this.scrollbarX = div(cls.element.thumb('x')); 1165 this.scrollbarXRail.appendChild(this.scrollbarX); 1166 this.scrollbarX.setAttribute('tabindex', 0); 1167 this.event.bind(this.scrollbarX, 'focus', focus); 1168 this.event.bind(this.scrollbarX, 'blur', blur); 1169 this.scrollbarXActive = null; 1170 this.scrollbarXWidth = null; 1171 this.scrollbarXLeft = null; 1172 var railXStyle = get(this.scrollbarXRail); 1173 this.scrollbarXBottom = parseInt(railXStyle.bottom, 10); 1174 if (isNaN(this.scrollbarXBottom)) { 1175 this.isScrollbarXUsingBottom = false; 1176 this.scrollbarXTop = toInt(railXStyle.top); 1177 } else { 1178 this.isScrollbarXUsingBottom = true; 1179 } 1180 this.railBorderXWidth = 1181 toInt(railXStyle.borderLeftWidth) + toInt(railXStyle.borderRightWidth); 1182 // Set rail to display:block to calculate margins 1183 set(this.scrollbarXRail, { display: 'block' }); 1184 this.railXMarginWidth = 1185 toInt(railXStyle.marginLeft) + toInt(railXStyle.marginRight); 1186 set(this.scrollbarXRail, { display: '' }); 1187 this.railXWidth = null; 1188 this.railXRatio = null; 1189 1190 this.scrollbarYRail = div(cls.element.rail('y')); 1191 element.appendChild(this.scrollbarYRail); 1192 this.scrollbarY = div(cls.element.thumb('y')); 1193 this.scrollbarYRail.appendChild(this.scrollbarY); 1194 this.scrollbarY.setAttribute('tabindex', 0); 1195 this.event.bind(this.scrollbarY, 'focus', focus); 1196 this.event.bind(this.scrollbarY, 'blur', blur); 1197 this.scrollbarYActive = null; 1198 this.scrollbarYHeight = null; 1199 this.scrollbarYTop = null; 1200 var railYStyle = get(this.scrollbarYRail); 1201 this.scrollbarYRight = parseInt(railYStyle.right, 10); 1202 if (isNaN(this.scrollbarYRight)) { 1203 this.isScrollbarYUsingRight = false; 1204 this.scrollbarYLeft = toInt(railYStyle.left); 1205 } else { 1206 this.isScrollbarYUsingRight = true; 1207 } 1208 this.scrollbarYOuterWidth = this.isRtl ? outerWidth(this.scrollbarY) : null; 1209 this.railBorderYWidth = 1210 toInt(railYStyle.borderTopWidth) + toInt(railYStyle.borderBottomWidth); 1211 set(this.scrollbarYRail, { display: 'block' }); 1212 this.railYMarginHeight = 1213 toInt(railYStyle.marginTop) + toInt(railYStyle.marginBottom); 1214 set(this.scrollbarYRail, { display: '' }); 1215 this.railYHeight = null; 1216 this.railYRatio = null; 1217 1218 this.reach = { 1219 x: 1220 element.scrollLeft <= 0 1221 ? 'start' 1222 : element.scrollLeft >= this.contentWidth - this.containerWidth 1223 ? 'end' 1224 : null, 1225 y: 1226 element.scrollTop <= 0 1227 ? 'start' 1228 : element.scrollTop >= this.contentHeight - this.containerHeight 1229 ? 'end' 1230 : null, 1231 }; 1232 1233 this.isAlive = true; 1234 1235 this.settings.handlers.forEach(function (handlerName) { return handlers[handlerName](this$1); }); 1236 1237 this.lastScrollTop = Math.floor(element.scrollTop); // for onScroll only 1238 this.lastScrollLeft = element.scrollLeft; // for onScroll only 1239 this.event.bind(this.element, 'scroll', function (e) { return this$1.onScroll(e); }); 1240 updateGeometry(this); 1241 }; 1242 1243 PerfectScrollbar.prototype.update = function update () { 1244 if (!this.isAlive) { 1245 return; 1246 } 1247 1248 // Recalcuate negative scrollLeft adjustment 1249 this.negativeScrollAdjustment = this.isNegativeScroll 1250 ? this.element.scrollWidth - this.element.clientWidth 1251 : 0; 1252 1253 // Recalculate rail margins 1254 set(this.scrollbarXRail, { display: 'block' }); 1255 set(this.scrollbarYRail, { display: 'block' }); 1256 this.railXMarginWidth = 1257 toInt(get(this.scrollbarXRail).marginLeft) + 1258 toInt(get(this.scrollbarXRail).marginRight); 1259 this.railYMarginHeight = 1260 toInt(get(this.scrollbarYRail).marginTop) + 1261 toInt(get(this.scrollbarYRail).marginBottom); 1262 1263 // Hide scrollbars not to affect scrollWidth and scrollHeight 1264 set(this.scrollbarXRail, { display: 'none' }); 1265 set(this.scrollbarYRail, { display: 'none' }); 1266 1267 updateGeometry(this); 1268 1269 processScrollDiff(this, 'top', 0, false, true); 1270 processScrollDiff(this, 'left', 0, false, true); 1271 1272 set(this.scrollbarXRail, { display: '' }); 1273 set(this.scrollbarYRail, { display: '' }); 1274 }; 1275 1276 PerfectScrollbar.prototype.onScroll = function onScroll (e) { 1277 if (!this.isAlive) { 1278 return; 1279 } 1280 1281 updateGeometry(this); 1282 processScrollDiff(this, 'top', this.element.scrollTop - this.lastScrollTop); 1283 processScrollDiff( 1284 this, 1285 'left', 1286 this.element.scrollLeft - this.lastScrollLeft 1287 ); 1288 1289 this.lastScrollTop = Math.floor(this.element.scrollTop); 1290 this.lastScrollLeft = this.element.scrollLeft; 1291 }; 1292 1293 PerfectScrollbar.prototype.destroy = function destroy () { 1294 if (!this.isAlive) { 1295 return; 1296 } 1297 1298 this.event.unbindAll(); 1299 remove(this.scrollbarX); 1300 remove(this.scrollbarY); 1301 remove(this.scrollbarXRail); 1302 remove(this.scrollbarYRail); 1303 this.removePsClasses(); 1304 1305 // unset elements 1306 this.element = null; 1307 this.scrollbarX = null; 1308 this.scrollbarY = null; 1309 this.scrollbarXRail = null; 1310 this.scrollbarYRail = null; 1311 1312 this.isAlive = false; 1313 }; 1314 1315 PerfectScrollbar.prototype.removePsClasses = function removePsClasses () { 1316 this.element.className = this.element.className 1317 .split(' ') 1318 .filter(function (name) { return !name.match(/^ps([-_].+|)$/); }) 1319 .join(' '); 1320 }; 1321 1322 return PerfectScrollbar; 1323 1324 })));