1WS.Namespace.Create('WS.ScrollBar', (function () { 2 3 var SCROLL_DELTA = 100; 4 var SCROLL_DURATION = 200; 5 var CSS_ELEMENT = 'sb-scroll'; 6 var CSS_ELEMENT_INIT = 'sb-i'; 7 var CSS_ELEMENT_RELATIVE = 'sb-rel'; 8 var CSS_SCROLLBAR = 'sb-scrollbar'; 9 var CSS_HANDLE = 'sb-handle'; 10 var CSS_FULL = 'sb-full'; 11 var CSS_ACTIVE = 'sb-active'; 12 var IE_VERSION = GetIEVersion(); 13 var isIE = IE_VERSION > 0 && IE_VERSION <= 11; 14 15 var __scrollbars = []; 16 var min = Math.min, max = Math.max; 17 18 function ScrollBar(element) { 19 this._element = (typeof element === 'string') ? document.querySelector(element) : element; 20 this._bar = null; 21 this._stopAnimation = null; 22 this._scrollTop = 0; 23 this._scrollAnimationFrame = null; 24 this._initialized = false; 25 26 if (!isIE) { 27 this._onScroll = onScroll.bind(this); 28 this._onMouseOver = isIE ? onMouseOverIE.bind(this) : onMouseOver.bind(this); 29 this._onMouseWheel = onMouseWheel.bind(this); 30 this._onDragStart = onDragStart.bind(this); 31 this._updateBar = updateBar.bind(this); 32 } 33 34 // if (this._element !== undefined && this._element.tabIndex < 0) this._element.tabIndex = -1; 35 36 if (this._element) { 37 38 __scrollbars.push(this); 39 40 init.call(this); 41 bindEvents.call(this); 42 this.update(); 43 } 44 } 45 46 function init() { 47 var element = this._element; 48 this._scrollTop = this._element.scrollTop; 49 this._bar = element.appendChild(renderBar()); 50 51 element.classList.add(CSS_ELEMENT); 52 element.classList.add(CSS_ELEMENT_INIT); 53 54 if (isIE) { 55 element.classList.add('sb-scroll-ie'); 56 } 57 58 if (window.getComputedStyle(element).position === 'static') { 59 element.classList.add(CSS_ELEMENT_RELATIVE); 60 } 61 this._initialized = true; 62 } 63 64 function bindEvents() { 65 if (isIE) return; 66 this._element.addEventListener('scroll', this._onScroll); 67 this._element.addEventListener('mouseover', this._onMouseOver); 68 this._element.addEventListener('scrollbar:mousewheel', this._onMouseWheel, { passive: false }); 69 this._element.addEventListener('keydown', onKeyDown); 70 this._bar.addEventListener('mousedown', this._onDragStart); 71 } 72 73 function unbindEvents() { 74 if (isIE) return; 75 this._element.removeEventListener('scroll', this._onScroll); 76 this._element.removeEventListener('mouseover', this._onMouseOver); 77 this._element.removeEventListener('scrollbar:mousewheel', this._onMouseWheel); 78 this._element.removeEventListener('keydown', onKeyDown); 79 this._bar.removeEventListener('mousedown', this._onDragStart); 80 } 81 82 function setScrollTop(scrollTop) { 83 this._preventNextUpdate = true; 84 this._element.scrollTop = scrollTop; 85 updateBar.call(this); 86 } 87 88 function updateBar() { 89 var element = this._element; 90 var handle = this._bar.children[0]; 91 var maxHeight = this._bar.offsetHeight; 92 var maxTop = (maxHeight - handle.offsetHeight) / maxHeight; 93 var top = (element.scrollTop) / (element.scrollHeight - element.offsetHeight) * maxTop; 94 95 this._bar.style.transform = 'translateY(' + element.scrollTop + 'px)'; 96 handle.style.top = (top * 100) + '%'; 97 } 98 99 function updateHandleHeight() { 100 var element = this._element; 101 var isFull = element.offsetHeight >= element.scrollHeight; 102 this._bar.classList.toggle(CSS_FULL, isFull); 103 this._bar.children[0].style.height = (element.offsetHeight / element.scrollHeight * 100) + '%'; 104 } 105 106 function removeEl(element) { 107 return (element.parentElement) ? 108 element.parentElement.removeChild(element) : null; 109 } 110 111 function onKeyDown(event) { 112 var keyCode = ('which' in event) ? event.which : event.keyCode; 113 var scrollbar = ScrollBar.get(event.currentTarget); 114
115 if (!scrollbar || keyCode < 33 || keyCode > 40) return; 116 117 event.preventDefault(); 118 event.stopPropagation(); 119 120 switch (keyCode) { 121 case 33: return scrollbar.pageUp(true); 122 case 34: return scrollbar.pageDown(true); 123 case 35: return scrollbar.scrollToEnd(true); 124 case 36: return scrollbar.scrollTo(0, true); 125 case 38: return scrollbar.scrollTo(scrollbar._scrollTop - SCROLL_DELTA, true); 126 case 40: return scrollbar.scrollTo(scrollbar._scrollTop + SCROLL_DELTA, true); 127 default: return; 128 } 129 } 130 131 function onScroll(e) { 132 if (!this._initialized) return; 133 if (this._preventNextUpdate) return this._preventNextUpdate = false; 134 this._scrollAnimationFrame = requestAnimationFrame(this._updateBar); 135 this._scrollTop = this._element.scrollTop; 136 } 137 138 function onMouseOver() { 139 this.update(); 140 } 141 142 var isOver = false; 143 function onMouseOverIE() { 144 if (isOver) { 145 return; 146 } 147 this.update(); 148 } 149 150 function onDragStart(e) { 151 var element = this._element; 152 var mousePos = getMousePos(e); 153 var drag = ScrollBar.dragData; 154 155 if (!ScrollBar.activeBar && element) { 156 var handle = this._bar.children[0]; 157 158 if (e.target === this._bar) { 159 var dir = (handle.getBoundingClientRect().top < mousePos[1] ? 1 : -1); 160 this.scrollTo((element.scrollTop + element.offsetHeight * dir), true); 161 } else { 162 onDragEnd(); 163 this._element.classList.add(CSS_ACTIVE); 164 165 ScrollBar.activeBar = this; 166 167 drag.maxDeltaTop = 1 - (handle.offsetHeight / this._bar.offsetHeight); 168 drag.containerHeight = element.offsetHeight; 169 drag.height = drag.containerHeight - element.scrollHeight; 170 171 drag.initialY = mousePos[1]; 172 drag.initialTop = handle.offsetTop / drag.containerHeight; 173 174 document.addEventListener('mousemove', onDragMove); 175 document.addEventListener('mouseup', onDragEnd); 176 } 177 } 178 } 179 180 function onDragMove(e) { 181 var activeBar; 182 var mousePos = getMousePos(e); 183 var drag = ScrollBar.dragData; 184 185 if (activeBar = ScrollBar.activeBar) { 186 var deltaY = drag.initialY - mousePos[1]; 187 var top = (drag.initialTop - (deltaY / drag.containerHeight)); 188 189 top *= (activeBar._element.scrollHeight - drag.containerHeight) / drag.maxDeltaTop; 190 191 activeBar.scrollTo(top); 192 } else { 193 onDragEnd(); 194 } 195 } 196 197 function onDragEnd() { 198 document.removeEventListener('mousemove', onDragMove); 199 document.removeEventListener('mouseup', onDragEnd); 200 201 if (ScrollBar.activeBar) { 202 ScrollBar.activeBar._element.classList.remove(CSS_ACTIVE); 203 ScrollBar.activeBar = null; 204 } 205 } 206 207 function onMouseWheel(e) { 208 var element = e.currentTarget; 209 var deltaY = e.detail.deltaY; 210 211 if ((deltaY < 0 && element.scrollTop + element.offsetHeight < element.scrollHeight) 212 || (deltaY > 0 && element.scrollTop > 0)) { 213 e.preventDefault(); 214 e.stopPropagation(); 215 } 216 217 updateHandleHeight.call(this); 218 this.scrollTo(this._scrollTop + (e.detail.deltaFactor * -deltaY), Math.abs(deltaY) === 1); 219 } 220 221 function animateScrollTo(to, duration, complete) { 222 var element = this._element; 223 var start = element.scrollTop; 224 var distance = to - start; 225 var startTime = null; 226 var stopped = false; 227 228 var self = this; 229 var animateScroll = function (timestamp) { 230 if (stopped) return; 231 if (!startTime) { 232 startTime = timestamp; 233 } 234 235 var progress = timestamp - startTime; 236 237 if (progress < duration) { 238 requestAnimationFrame(animateScroll); 239 var delta = distance > 0 ? 240 max(distance * easeOutQuad(progress / duration), 1) : 241 min(distance * easeOutQuad(progress / duration), -1);
242 var val = parseInt(start + delta); 243 setScrollTop.call(self, val); 244 } else { 245 self._stopAnimation = null; 246 setScrollTop.call(self, self._scrollTop); 247 complete(); 248 } 249 }; 250 251 this._stopAnimation = function () { stopped = true }; 252 253 requestAnimationFrame(animateScroll); 254 } 255 256 function renderBar() { 257 var scrollbar = document.createElement('div'); 258 var handle = document.createElement('div'); 259 scrollbar.className = CSS_SCROLLBAR; 260 handle.className = CSS_HANDLE; 261 scrollbar.appendChild(handle); 262 return scrollbar; 263 } 264 265 function getMousePos(event) { 266 return [event.clientX, event.clientY]; 267 } 268 269 function easeOutQuad(t) { 270 return t * (2 - t); 271 } 272 273 /** 274 * Private CustomEvent 'scrollbar:mousewheel' 275 */ 276 (function () { 277 if (document.attachEvent) 278 document.attachEvent('onwheel', dispatchMouseWheelEvent); 279 else if (document.addEventListener) 280 document.addEventListener('wheel', dispatchMouseWheelEvent, { passive: false, useCapture: false }); 281 282 var createCustomEvent = isIE ? function (eventName, options) { 283 var customEvent = document.createEvent('CustomEvent'); 284 customEvent.initCustomEvent(eventName, options.bubbles, options.cancelable, { 285 detail: options.detail 286 }); 287 return customEvent; 288 } : function (eventName, options) { return new CustomEvent(eventName, options) }; 289 290 function dispatchMouseWheelEvent(e) { 291 var closest = null; 292 var deltaY = 0; 293 294 if (!e) e = window.event; 295 if (e.wheelDelta) deltaY = e.wheelDelta / 120; 296 else if (e.detail) deltaY = -e.detail / 3; 297 298 //var scrollableElement = e.composedPath ? 299 // findScrollableInPath(e.composedPath()) : 300 // findScrollableParent(e.target); 301 302 var customEvent = createCustomEvent('scrollbar:mousewheel', { 303 bubbles: true, 304 cancelable: true, 305 detail: { 306 deltaY: deltaY, 307 deltaFactor: SCROLL_DELTA 308 } 309 }); 310 311 //if (scrollableElement && !scrollableElement.classList.contains(CSS_ELEMENT)) { 312 // return false; 313 //} 314 315 if (!e.target.dispatchEvent(customEvent) && e.preventDefault) { 316 e.preventDefault(); 317 } 318 } 319 320 function isScrollable(element) { 321 var overflowY = window.getComputedStyle(element).overflowY; 322 if (element.classList.contains(CSS_ELEMENT) || overflowY === 'auto' || overflowY === 'scroll') { 323 return true; 324 } 325 return false; 326 } 327 328 function findScrollableInPath(eventPath) { 329 if (eventPath) { 330 var len = len = eventPath.indexOf(document.body) || eventPath.length 331 for (var i = 0; i < len; i++) { 332 if (isScrollable(eventPath[i])) return eventPath[i]; 333 } 334 } 335 return null; 336 } 337 338 function findScrollableParent(parent) { 339 if (parent) { 340 if (isScrollable(parent)) return parent; 341 while ((parent = parent.parentElement) && parent !== document.body) { 342 if (isScrollable(parent)) return parent; 343 } 344 } 345 return null; 346 } 347 })(); 348 349 function GetIEVersion() { 350 var sAgent = window.navigator.userAgent; 351 var Idx = sAgent.indexOf("MSIE"); 352 353 // If IE, return version number. 354 if (Idx > 0) 355 return parseInt(sAgent.substring(Idx + 5, sAgent.indexOf(".", Idx))); 356 357 // If IE 11 then look for Updated user agent string. 358 else if (!!navigator.userAgent.match(/Trident\/7\./)) 359 return 11; 360 361 else 362 return 0; //It is not IE 363 } 364 365 /** 366 * Scrolls to the specified value. 367 * @param {number} scrollTop Element scrollTop property value 368 * @param {boolean|number} duration Animation duration. Set true for default duration. 369 */ 370 function scrollTo(scrollTop, duration) { 371 var self = this; 372 var element = this._element; 373 374 if (this._stopAnimation) this._stopAnimation(); 375 scrollTop = this._scrollTop = min(max(scrollTop, 0), element.scrollHeight - element.offsetHeight); 376 377 if (!isIE && duration) { 378 if (duration === true) duration = SCROLL_DURATION; 379 animateScrollTo.call(this, scrollTop, duration, function () { 380 self._preventNextUpdate = false; 381 }); 382 } else { 383 setScrollTop.call(this, scrollTop); 384 } 385 } 386 387 function scrollToEnd(animated) { 388 return this.scrollTo(this._element.scrollHeight, animated); 389 } 390 391 /** 392 * Scrolls up using the container offset height. 393 * @param {boolean} animated Set true for animated scroll 394 */ 395 function pageUp(animated) { 396 this.pageScroll(-1, animated); 397 } 398 399 /** 400 * Scrolls down using the container offsetHeight. 401 * @param {boolean} animated Set true for animated scroll 402 */ 403 function pageDown(animated) { 404 this.pageScroll(1, animated); 405 } 406 407 /** 408 * Scrolls down using the container offsetHeight. 409 * @param {number} multiplier Scroll offsetHeight multiplier 410 * @param {boolean} animated Set true for animated scroll 411 */ 412 function pageScroll(multiplier, animated) { 413 this.scrollTo(this._scrollTop + this._element.offsetHeight * multiplier, animated); 414 } 415 416 /** 417 * Refreshes the scrollbar style. 418 */
419 function update() { 420 if (!this._initialized) return; 421 if (this._bar.parentElement !== this._element) { 422 this._element.appendChild(this._bar); 423 } 424 updateHandleHeight.call(this); 425 updateBar.call(this); 426 } 427 428 /** 429 * Destroys the scrollbar and clears its event handlers. 430 */ 431 function destroy() { 432 if (!this._initialized) return; 433 if (this._stopAnimation) this._stopAnimation(); 434 if (this._scrollAnimationFrame) cancelAnimationFrame(this._scrollAnimationFrame); 435 if (this._element && ScrollBar.activeBar === this._element) onDragEnd(); 436 437 unbindEvents.call(this); 438 439 this._element.classList.remove( 440 CSS_ELEMENT, 441 CSS_ELEMENT_INIT, 442 CSS_ELEMENT_RELATIVE 443 ); 444 445 if (removeEl(this._bar)) { 446 var scrollbarIndex = __scrollbars.indexOf(this); 447 448 this._element = null; 449 this._bar = null; 450 451 if (scrollbarIndex >= 0) { 452 __scrollbars.splice(scrollbarIndex, 1); 453 } 454 455 this._initialized = false; 456 return true; 457 } 458 459 return false; 460 } 461 462 ScrollBar.activeBar = null; 463 464 ScrollBar.dragData = { 465 containerHeight: 0, 466 maxTop: 0, 467 initialTop: 0, 468 initialY: 0, 469 deltaY: 0 470 }; 471 472 ScrollBar.__scrollbars = __scrollbars; 473 474 /** 475 * Returns the attached ScrollBar instance using the specified element. 476 * @param {HtmlElement} element The scrollable container 477 */ 478 ScrollBar.get = function (element) { 479 if (typeof element === 'string') { 480 element = document.querySelector(element); 481 } 482 for (var i = 0, len = __scrollbars.length; i < len; i++) { 483 if (__scrollbars[i]._element === element) { 484 return __scrollbars[i]; 485 } 486 } 487 return null; 488 }; 489 490 /** 491 * Initializes a new ScrollBar instance into a container and makes it scrollable. 492 * @param {HtmlElement} element The scrollable container 493 * @param {boolean} forced Set true to force a new instance and destroy the old one. 494 */ 495 ScrollBar.init = function (element, forced) { 496 var scrollbar = ScrollBar.get(element); 497 498 if (forced && scrollbar) scrollbar.destroy(); 499 500 if (!scrollbar) { 501 scrollbar = new ScrollBar(element); 502 } 503 504 return scrollbar; 505 }; 506 507 /** 508 * Initializes all selected elements matching the selector below: 509 * ```css 510 * .sb-scroll,[sb-scroll],[data-sb-scroll] 511 * ``` 512 * @param {boolean} forced Set true to force all new instances. 513 */ 514 ScrollBar.initAll = function (forced) { 515 var containers = document.querySelectorAll('.sb-scroll,[sb-scroll],[data-sb-scroll]'); 516 var scrollbars = []; 517 for (var i = 0, len = containers.length; i < len; i++) { 518 scrollbars.push(ScrollBar.init(containers[i], forced)); 519 } 520 return scrollbars; 521 }; 522 523 /** 524 * Destroys the registered ScrollBar instance of the element. 525 * @param {HtmlElement} element The scrollable container. 526 */ 527 ScrollBar.destroy = function (element) { 528 var scrollbar = ScrollBar.get(element); 529 if (scrollbar) scrollbar.destroy(); 530 return !!scrollbar; 531 }; 532 533 /** 534 * Destroys all registered ScrollBar instances. 535 */ 536 ScrollBar.destroyAll = function () { 537 var scrollbars = [].concat(__scrollbars); 538 var destroyedCount = 0; 539 for (var i = 0, len = scrollbars.length; i < len; i++) { 540 if (scrollbars[i].destroy()) destroyedCount++; 541 } 542 return destroyedCount; 543 }; 544 545 /** 546 * Polyfill for requestAnimationFrame 547 */ 548 (function () { 549 var lastTime = 0; 550 var vendors = ['ms', 'moz', 'webkit', 'o']; 551 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 552 window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 553 window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] 554 || window[vendors[x] + 'CancelRequestAnimationFrame']; 555 } 556 557 if (!window.requestAnimationFrame) 558 window.requestAnimationFrame = function (callback, element) { 559 var currTime = new Date().getTime(); 560 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 561 var id = window.setTimeout(function () { callback(currTime + timeToCall); }, 562 timeToCall); 563 lastTime = currTime + timeToCall; 564 return id; 565 }; 566 567 if (!window.cancelAnimationFrame) 568 window.cancelAnimationFrame = function (id) { 569 clearTimeout(id); 570 }; 571 }()); 572 573 window.addEventListener('load', function () { ScrollBar.initAll(false) }, false); 574 575 ScrollBar.prototype.scrollTo = scrollTo; 576 ScrollBar.prototype.scrollToEnd = scrollToEnd; 577 ScrollBar.prototype.pageUp = pageUp; 578 ScrollBar.prototype.pageDown = pageDown; 579 ScrollBar.prototype.pageScroll = pageScroll; 580 ScrollBar.prototype.update = update; 581 ScrollBar.prototype.destroy = destroy; 582 583 return ScrollBar; 584})());
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.