1/* Copyright (c) 2012 HyeonJe Jun (http://github.com/noraesae) 2 * Licensed under the MIT License 3 */ 4'use strict'; 5(function (factory) { 6 if (typeof define === 'function' && define.amd) { 7 // AMD. Register as an anonymous module. 8 define(['jquery'], factory); 9 } else { 10 // Browser globals 11 factory(jQuery); 12 } 13}(function ($) { 14 15 // The default settings for the plugin 16 var defaultSettings = { 17 wheelSpeed: 10, 18 wheelPropagation: false, 19 minScrollbarLength: null, 20 useBothWheelAxes: false, 21 useKeyboard: true, 22 suppressScrollX: false, 23 suppressScrollY: false, 24 scrollXMarginOffset: 0, 25 scrollYMarginOffset: 0 26 }; 27 28 var getEventClassName = (function () { 29 var incrementingId = 0; 30 return function () { 31 var id = incrementingId; 32 incrementingId += 1; 33 return '.perfect-scrollbar-' + id; 34 }; 35 }()); 36 37 $.fn.perfectScrollbar = function (suppliedSettings, option) { 38 39 return this.each(function () { 40 // Use the default settings 41 var settings = $.extend(true, {}, defaultSettings), 42 $this = $(this); 43 44 if (typeof suppliedSettings === "object") { 45 // But over-ride any supplied 46 $.extend(true, settings, suppliedSettings); 47 } else { 48 // If no settings were supplied, then the first param must be the option 49 option = suppliedSettings; 50 } 51 52 // Catch options 53 54 if (option === 'update') { 55 if ($this.data('perfect-scrollbar-update')) { 56 $this.data('perfect-scrollbar-update')(); 57 } 58 return $this; 59 } 60 else if (option === 'destroy') { 61 if ($this.data('perfect-scrollbar-destroy')) { 62 $this.data('perfect-scrollbar-destroy')(); 63 } 64 return $this; 65 } 66 67 if ($this.data('perfect-scrollbar')) { 68 // if there's already perfect-scrollbar 69 return $this.data('perfect-scrollbar'); 70 } 71 72 73 // Or generate new perfectScrollbar 74 75 // Set class to the container 76 $this.addClass('ps-container'); 77 78 var $scrollbarXRail = $("<div class='ps-scrollbar-x-rail'></div>").appendTo($this), 79 $scrollbarYRail = $("<div class='ps-scrollbar-y-rail'></div>").appendTo($this), 80 $scrollbarX = $("<div class='ps-scrollbar-x'></div>").appendTo($scrollbarXRail), 81 $scrollbarY = $("<div class='ps-scrollbar-y'></div>").appendTo($scrollbarYRail), 82 scrollbarXActive, 83 scrollbarYActive, 84 containerWidth, 85 containerHeight, 86 contentWidth, 87 contentHeight, 88 scrollbarXWidth, 89 scrollbarXLeft, 90 scrollbarXBottom = parseInt($scrollbarXRail.css('bottom'), 10), 91 scrollbarYHeight, 92 scrollbarYTop, 93 scrollbarYRight = parseInt($scrollbarYRail.css('right'), 10), 94 eventClassName = getEventClassName(); 95 96 var updateContentScrollTop = function (currentTop, deltaY) { 97 var newTop = currentTop + deltaY, 98 maxTop = containerHeight - scrollbarYHeight; 99 100 if (newTop < 0) { 101 scrollbarYTop = 0; 102 } 103 else if (newTop > maxTop) { 104 scrollbarYTop = maxTop; 105 } 106 else { 107 scrollbarYTop = newTop; 108 } 109 110 var scrollTop = parseInt(scrollbarYTop * (contentHeight - containerHeight) / (containerHeight - scrollbarYHeight), 10); 111 $this.scrollTop(scrollTop); 112 $scrollbarXRail.css({bottom: scrollbarXBottom - scrollTop}); 113 114 // collmod 115 $(window).trigger('coll-scroll', [scrollTop]) 116 }; 117 118 var updateContentScrollLeft = function (currentLeft, deltaX) { 119 var newLeft = currentLeft + deltaX, 120 maxLeft = containerWidth - scrollbarXWidth; 121 122 if (newLeft < 0) { 123 scrollbarXLeft = 0; 124 } 125 else if (newLeft > maxLeft) { 126 scrollbarXLeft = maxLeft; 127 } 128 else { 129 scrollbarXLeft = newLeft; 130 } 131 132 var scrollLeft = parseInt(scrollbarXLeft * (contentWidth - containerWidth) / (containerWidth - scrollbarXWidth), 10); 133 $this.scrollLeft(scrollLeft); 134 $scrollbarYRail.css({right: scrollbarYRight - scrollLeft}); 135 }; 136 137 var getSettingsAdjustedThumbSize = function (thumbSize) { 138 if (settings.minScrollbarLength) { 139 thumbSize = Math.max(thumbSize, settings.minScrollbarLength); 140 } 141 return thumbSize; 142 }; 143 144 var updateScrollbarCss = function () { 145 $scrollbarXRail.css({left: $this.scrollLeft(), bottom: scrollbarXBottom - $this.scrollTop(), width: containerWidth, display: scrollbarXActive ? "inherit" : "none"}); 146 $scrollbarYRail.css({top: $this.scrollTop(), right: scrollbarYRight - $this.scrollLeft(), height: containerHeight, display: scrollbarYActive ? "inherit" : "none"}); 147 $scrollbarX.css({left: scrollbarXLeft, width: scrollbarXWidth}); 148 $scrollbarY.css({top: scrollbarYTop, height: scrollbarYHeight}); 149 150 151 // collmod 152 // $(window).trigger('coll-scroll', [$this.scrollTop()]) 153 }; 154 155 var updateBarSizeAndPosition = function () { 156 containerWidth = $this.width(); 157 containerHeight = $this.height(); 158 contentWidth = $this.prop('scrollWidth'); 159 contentHeight = $this.prop('scrollHeight'); 160 161 if (!settings.suppressScrollX && containerWidth + settings.scrollXMarginOffset < contentWidth) { 162 scrollbarXActive = true; 163 scrollbarXWidth = getSettingsAdjustedThumbSize(parseInt(containerWidth * containerWidth / contentWidth, 10)); 164 scrollbarXLeft = parseInt($this.scrollLeft() * (containerWidth - scrollbarXWidth) / (contentWidth - containerWidth), 10); 165 } 166 else { 167 scrollbarXActive = false;
168 scrollbarXWidth = 0; 169 scrollbarXLeft = 0; 170 $this.scrollLeft(0); 171 } 172 173 if (!settings.suppressScrollY && containerHeight + settings.scrollYMarginOffset < contentHeight) { 174 scrollbarYActive = true; 175 scrollbarYHeight = getSettingsAdjustedThumbSize(parseInt(containerHeight * containerHeight / contentHeight, 10)); 176 scrollbarYTop = parseInt($this.scrollTop() * (containerHeight - scrollbarYHeight) / (contentHeight - containerHeight), 10); 177 } 178 else { 179 scrollbarYActive = false; 180 scrollbarYHeight = 0; 181 scrollbarYTop = 0; 182 $this.scrollTop(0); 183 } 184 185 if (scrollbarYTop >= containerHeight - scrollbarYHeight) { 186 scrollbarYTop = containerHeight - scrollbarYHeight; 187 } 188 if (scrollbarXLeft >= containerWidth - scrollbarXWidth) { 189 scrollbarXLeft = containerWidth - scrollbarXWidth; 190 } 191 192 updateScrollbarCss(); 193 }; 194 195 var bindMouseScrollXHandler = function () { 196 var currentLeft, 197 currentPageX; 198 199 $scrollbarX.bind('mousedown' + eventClassName, function (e) { 200 currentPageX = e.pageX; 201 currentLeft = $scrollbarX.position().left; 202 $scrollbarXRail.addClass('in-scrolling'); 203 e.stopPropagation(); 204 e.preventDefault(); 205 }); 206 207 $(document).bind('mousemove' + eventClassName, function (e) { 208 if ($scrollbarXRail.hasClass('in-scrolling')) { 209 updateContentScrollLeft(currentLeft, e.pageX - currentPageX); 210 e.stopPropagation(); 211 e.preventDefault(); 212 } 213 }); 214 215 $(document).bind('mouseup' + eventClassName, function (e) { 216 if ($scrollbarXRail.hasClass('in-scrolling')) { 217 $scrollbarXRail.removeClass('in-scrolling'); 218 } 219 }); 220 221 currentLeft = 222 currentPageX = null; 223 }; 224 225 var bindMouseScrollYHandler = function () { 226 var currentTop, 227 currentPageY; 228 229 $scrollbarY.bind('mousedown' + eventClassName, function (e) { 230 currentPageY = e.pageY; 231 currentTop = $scrollbarY.position().top; 232 $scrollbarYRail.addClass('in-scrolling'); 233 e.stopPropagation(); 234 e.preventDefault(); 235 }); 236 237 $(document).bind('mousemove' + eventClassName, function (e) { 238 if ($scrollbarYRail.hasClass('in-scrolling')) { 239 updateContentScrollTop(currentTop, e.pageY - currentPageY); 240 e.stopPropagation(); 241 e.preventDefault(); 242 } 243 }); 244 245 $(document).bind('mouseup' + eventClassName, function (e) { 246 if ($scrollbarYRail.hasClass('in-scrolling')) { 247 $scrollbarYRail.removeClass('in-scrolling'); 248 } 249 }); 250 251 currentTop = 252 currentPageY = null; 253 }; 254 255 // check if the default scrolling should be prevented. 256 var shouldPreventDefault = function (deltaX, deltaY) { 257 var scrollTop = $this.scrollTop(); 258 if (deltaX === 0) { 259 if (!scrollbarYActive) { 260 return false; 261 } 262 if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= contentHeight - containerHeight && deltaY < 0)) { 263 return !settings.wheelPropagation; 264 } 265 } 266 267 var scrollLeft = $this.scrollLeft(); 268 if (deltaY === 0) { 269 if (!scrollbarXActive) { 270 return false;
271 } 272 if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= contentWidth - containerWidth && deltaX > 0)) { 273 return !settings.wheelPropagation; 274 } 275 } 276 return true; 277 }; 278 279 // bind handlers 280 var bindMouseWheelHandler = function () { 281 var shouldPrevent = false; 282 $this.bind('mousewheel' + eventClassName, function (e, deprecatedDelta, deprecatedDeltaX, deprecatedDeltaY) { 283 var deltaX = e.deltaX ? e.deltaX / 10 : deprecatedDeltaX, 284 deltaY = e.deltaY ? e.deltaY / 10 : deprecatedDeltaY; 285 286 if (!settings.useBothWheelAxes) { 287 // deltaX will only be used for horizontal scrolling and deltaY will 288 // only be used for vertical scrolling - this is the default 289 $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); 290 $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); 291 } else if (scrollbarYActive && !scrollbarXActive) { 292 // only vertical scrollbar is active and useBothWheelAxes option is 293 // active, so let's scroll vertical bar using both mouse wheel axes 294 if (deltaY) { 295 $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); 296 } else { 297 $this.scrollTop($this.scrollTop() + (deltaX * settings.wheelSpeed)); 298 } 299 } else if (scrollbarXActive && !scrollbarYActive) { 300 // useBothWheelAxes and only horizontal bar is active, so use both 301 // wheel axes for horizontal bar 302 if (deltaX) { 303 $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); 304 } else { 305 $this.scrollLeft($this.scrollLeft() - (deltaY * settings.wheelSpeed)); 306 } 307 } 308 309 // update bar position 310 updateBarSizeAndPosition(); 311 312 shouldPrevent = shouldPreventDefault(deltaX, deltaY); 313 if (shouldPrevent) { 314 e.preventDefault(); 315 } 316 }); 317 318 // fix Firefox scroll problem 319 $this.bind('MozMousePixelScroll' + eventClassName, function (e) { 320 if (shouldPrevent) { 321 e.preventDefault(); 322 } 323 }); 324 }; 325 326 var bindKeyboardHandler = function () { 327 var hovered = false; 328 $this.bind('mouseenter' + eventClassName, function (e) { 329 hovered = true; 330 }); 331 $this.bind('mouseleave' + eventClassName, function (e) { 332 hovered = false; 333 }); 334 335 var shouldPrevent = false; 336 $(document).bind('keydown' + eventClassName, function (e) { 337 if (!hovered) { 338 return; 339 } 340 341 var deltaX = 0, 342 deltaY = 0; 343 344 switch (e.which) { 345 case 37: // left 346 deltaX = -3; 347 break; 348 case 38: // up 349 deltaY = 3; 350 break; 351 case 39: // right 352 deltaX = 3; 353 break; 354 case 40: // down 355 deltaY = -3; 356 break; 357 case 33: // page up 358 deltaY = 9; 359 break; 360 case 32: // space bar 361 case 34: // page down 362 deltaY = -9; 363 break; 364 case 35: // end 365 deltaY = -containerHeight; 366 break; 367 case 36: // home 368 deltaY = containerHeight; 369 break; 370 default: 371 return; 372 } 373 374 $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); 375 $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); 376 377 shouldPrevent = shouldPreventDefault(deltaX, deltaY); 378 if (shouldPrevent) { 379 e.preventDefault(); 380 } 381 }); 382 }; 383 384 var bindRailClickHandler = function () { 385 var stopPropagation = function (e) { 386 e.stopPropagation(); 387 }; 388
389 $scrollbarY.bind('click' + eventClassName, stopPropagation); 390 $scrollbarYRail.bind('click' + eventClassName, function (e) { 391 var halfOfScrollbarLength = parseInt(scrollbarYHeight / 2, 10), 392 positionTop = e.pageY - $scrollbarYRail.offset().top - halfOfScrollbarLength, 393 maxPositionTop = containerHeight - scrollbarYHeight, 394 positionRatio = positionTop / maxPositionTop; 395 396 if (positionRatio < 0) { 397 positionRatio = 0; 398 } else if (positionRatio > 1) { 399 positionRatio = 1; 400 } 401 402 $this.scrollTop((contentHeight - containerHeight) * positionRatio); 403 404 // collmod 405 $(window).trigger('coll-scroll', [(contentHeight - containerHeight) * positionRatio]) 406 407 }); 408 409 $scrollbarX.bind('click' + eventClassName, stopPropagation); 410 $scrollbarXRail.bind('click' + eventClassName, function (e) { 411 var halfOfScrollbarLength = parseInt(scrollbarXWidth / 2, 10), 412 positionLeft = e.pageX - $scrollbarXRail.offset().left - halfOfScrollbarLength, 413 maxPositionLeft = containerWidth - scrollbarXWidth, 414 positionRatio = positionLeft / maxPositionLeft; 415 416 if (positionRatio < 0) { 417 positionRatio = 0; 418 } else if (positionRatio > 1) { 419 positionRatio = 1; 420 } 421 422 $this.scrollLeft((contentWidth - containerWidth) * positionRatio); 423 }); 424 }; 425 426 // bind mobile touch handler 427 var bindMobileTouchHandler = function () { 428 var applyTouchMove = function (differenceX, differenceY) { 429 $this.scrollTop($this.scrollTop() - differenceY); 430 $this.scrollLeft($this.scrollLeft() - differenceX); 431 432 // update bar position 433 updateBarSizeAndPosition(); 434 }; 435 436 var startCoords = {}, 437 startTime = 0, 438 speed = {}, 439 breakingProcess = null, 440 inGlobalTouch = false; 441 442 $(window).bind("touchstart" + eventClassName, function (e) { 443 inGlobalTouch = true; 444 }); 445 $(window).bind("touchend" + eventClassName, function (e) { 446 inGlobalTouch = false; 447 }); 448 449 $this.bind("touchstart" + eventClassName, function (e) { 450 var touch = e.originalEvent.targetTouches[0]; 451 452 startCoords.pageX = touch.pageX; 453 startCoords.pageY = touch.pageY; 454 455 startTime = (new Date()).getTime(); 456 457 if (breakingProcess !== null) { 458 clearInterval(breakingProcess); 459 } 460 461 e.stopPropagation(); 462 }); 463 $this.bind("touchmove" + eventClassName, function (e) { 464 if (!inGlobalTouch && e.originalEvent.targetTouches.length === 1) { 465 var touch = e.originalEvent.targetTouches[0]; 466 467 var currentCoords = {}; 468 currentCoords.pageX = touch.pageX; 469 currentCoords.pageY = touch.pageY; 470
471 var differenceX = currentCoords.pageX - startCoords.pageX, 472 differenceY = currentCoords.pageY - startCoords.pageY; 473 474 applyTouchMove(differenceX, differenceY); 475 startCoords = currentCoords; 476 477 var currentTime = (new Date()).getTime(); 478 479 var timeGap = currentTime - startTime; 480 if (timeGap > 0) { 481 speed.x = differenceX / timeGap; 482 speed.y = differenceY / timeGap; 483 startTime = currentTime; 484 } 485 486 e.preventDefault(); 487 } 488 }); 489 $this.bind("touchend" + eventClassName, function (e) { 490 clearInterval(breakingProcess); 491 breakingProcess = setInterval(function () { 492 if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { 493 clearInterval(breakingProcess); 494 return; 495 } 496 497 applyTouchMove(speed.x * 30, speed.y * 30); 498 499 speed.x *= 0.8; 500 speed.y *= 0.8; 501 }, 10); 502 }); 503 }; 504 505 var bindScrollHandler = function () { 506 $this.bind('scroll' + eventClassName, function (e) { 507 updateBarSizeAndPosition(); 508 }); 509 }; 510 511 var destroy = function () { 512 $this.unbind(eventClassName); 513 $(window).unbind(eventClassName); 514 $(document).unbind(eventClassName); 515 $this.data('perfect-scrollbar', null); 516 $this.data('perfect-scrollbar-update', null); 517 $this.data('perfect-scrollbar-destroy', null); 518 $scrollbarX.remove(); 519 $scrollbarY.remove(); 520 $scrollbarXRail.remove(); 521 $scrollbarYRail.remove(); 522 523 // clean all variables 524 $scrollbarX = 525 $scrollbarY = 526 containerWidth = 527 containerHeight = 528 contentWidth = 529 contentHeight = 530 scrollbarXWidth = 531 scrollbarXLeft = 532 scrollbarXBottom = 533 scrollbarYHeight = 534 scrollbarYTop = 535 scrollbarYRight = null; 536 }; 537 538 var ieSupport = function (version) { 539 $this.addClass('ie').addClass('ie' + version); 540 541 var bindHoverHandlers = function () { 542 var mouseenter = function () { 543 $(this).addClass('hover'); 544 }; 545 var mouseleave = function () { 546 $(this).removeClass('hover'); 547 }; 548 $this.bind('mouseenter' + eventClassName, mouseenter).bind('mouseleave' + eventClassName, mouseleave); 549 $scrollbarXRail.bind('mouseenter' + eventClassName, mouseenter).bind('mouseleave' + eventClassName, mouseleave); 550 $scrollbarYRail.bind('mouseenter' + eventClassName, mouseenter).bind('mouseleave' + eventClassName, mouseleave); 551 $scrollbarX.bind('mouseenter' + eventClassName, mouseenter).bind('mouseleave' + eventClassName, mouseleave); 552 $scrollbarY.bind('mouseenter' + eventClassName, mouseenter).bind('mouseleave' + eventClassName, mouseleave); 553 }; 554 555 var fixIe6ScrollbarPosition = function () { 556 updateScrollbarCss = function () { 557 $scrollbarX.css({left: scrollbarXLeft + $this.scrollLeft(), bottom: scrollbarXBottom, width: scrollbarXWidth}); 558 $scrollbarY.css({top: scrollbarYTop + $this.scrollTop(), right: scrollbarYRight, height: scrollbarYHeight}); 559 $scrollbarX.hide().show(); 560 $scrollbarY.hide().show(); 561 }; 562 }; 563 564 if (version === 6) { 565 bindHoverHandlers(); 566 fixIe6ScrollbarPosition(); 567 } 568 }; 569 570 var supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch); 571 572 var initialize = function () { 573 var ieMatch = navigator.userAgent.toLowerCase().match(/(msie) ([\w.]+)/); 574 if (ieMatch && ieMatch[1] === 'msie') { 575 // must be executed at first, because 'ieSupport' may addClass to the container 576 ieSupport(parseInt(ieMatch[2], 10)); 577 } 578 579 updateBarSizeAndPosition(); 580 bindScrollHandler(); 581 bindMouseScrollXHandler(); 582 bindMouseScrollYHandler(); 583 bindRailClickHandler(); 584 if (supportsTouch) { 585 bindMobileTouchHandler(); 586 } 587 if ($this.mousewheel) { 588 bindMouseWheelHandler(); 589 } 590 if (settings.useKeyboard) { 591 bindKeyboardHandler(); 592 } 593 $this.data('perfect-scrollbar', $this); 594 $this.data('perfect-scrollbar-update', updateBarSizeAndPosition); 595 $this.data('perfect-scrollbar-destroy', destroy); 596 }; 597 598 // initialize 599 initialize(); 600 601 return $this; 602 }); 603 }; 604}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.