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