1/*! iScroll v5.2.0 ~ (c) 2008-2016 Matteo Spinelli ~ http://cubiq.org/license */ 2(function (window, document, Math) { 3var rAF = window.requestAnimationFrame || 4 window.webkitRequestAnimationFrame || 5 window.mozRequestAnimationFrame || 6 window.oRequestAnimationFrame || 7 window.msRequestAnimationFrame || 8 function (callback) { window.setTimeout(callback, 1000 / 60); }; 9 10var utils = (function () { 11 var me = {}; 12 13 var _elementStyle = document.createElement('div').style; 14 var _vendor = (function () { 15 var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'], 16 transform, 17 i = 0, 18 l = vendors.length; 19 20 for ( ; i < l; i++ ) { 21 transform = vendors[i] + 'ransform'; 22 if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1); 23 } 24 25 return false; 26 })(); 27 28 function _prefixStyle (style) { 29 if ( _vendor === false ) return false; 30 if ( _vendor === '' ) return style; 31 return _vendor + style.charAt(0).toUpperCase() + style.substr(1); 32 } 33 34 me.getTime = Date.now || function getTime () { return new Date().getTime(); }; 35 36 me.extend = function (target, obj) { 37 for ( var i in obj ) { 38 target[i] = obj[i]; 39 } 40 }; 41 42 me.addEvent = function (el, type, fn, capture) { 43 el.addEventListener(type, fn, !!capture); 44 }; 45 46 me.removeEvent = function (el, type, fn, capture) { 47 el.removeEventListener(type, fn, !!capture); 48 }; 49 50 me.prefixPointerEvent = function (pointerEvent) { 51 return window.MSPointerEvent ? 52 'MSPointer' + pointerEvent.charAt(7).toUpperCase() + pointerEvent.substr(8): 53 pointerEvent; 54 }; 55 56 me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) { 57 var distance = current - start, 58 speed = Math.abs(distance) / time, 59 destination, 60 duration; 61 62 deceleration = deceleration === undefined ? 0.0006 : deceleration; 63 64 destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); 65 duration = speed / deceleration; 66 67 if ( destination < lowerMargin ) { 68 destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; 69 distance = Math.abs(destination - current); 70 duration = distance / speed; 71 } else if ( destination > 0 ) { 72 destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; 73 distance = Math.abs(current) + destination; 74 duration = distance / speed; 75 } 76 77 return { 78 destination: Math.round(destination), 79 duration: duration 80 }; 81 }; 82 83 var _transform = _prefixStyle('transform'); 84 85 me.extend(me, { 86 hasTransform: _transform !== false, 87 hasPerspective: _prefixStyle('perspective') in _elementStyle, 88 hasTouch: 'ontouchstart' in window, 89 hasPointer: !!(window.PointerEvent || window.MSPointerEvent), // IE10 is prefixed 90 hasTransition: _prefixStyle('transition') in _elementStyle 91 }); 92 93 /* 94 This should find all Android browsers lower than build 535.19 (both stock browser and webview) 95 - galaxy S2 is ok 96 - 2.3.6 : `AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1` 97 - 4.0.4 : `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 98 - galaxy S3 is badAndroid (stock brower, webview) 99 `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 100 - galaxy S4 is badAndroid (stock brower, webview) 101 `AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30` 102 - galaxy S5 is OK 103 `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` 104 - galaxy S6 is OK 105 `AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36 (Chrome/)` 106 */ 107 me.isBadAndroid = (function() { 108 var appVersion = window.navigator.appVersion; 109 // Android browser is not a chrome browser. 110 if (/Android/.test(appVersion) && !(/Chrome\/\d/.test(appVersion))) { 111 var safariVersion = appVersion.match(/Safari\/(\d+.\d)/); 112 if(safariVersion && typeof safariVersion === "object" && safariVersion.length >= 2) { 113 return parseFloat(safariVersion[1]) < 535.19; 114 } else { 115 return true; 116 } 117 } else { 118 return false; 119 } 120 })(); 121 122 me.extend(me.style = {}, { 123 transform: _transform, 124 transitionTimingFunction: _prefixStyle('transitionTimingFunction'), 125 transitionDuration: _prefixStyle('transitionDuration'), 126 transitionDelay: _prefixStyle('transitionDelay'), 127 transformOrigin: _prefixStyle('transformOrigin') 128 }); 129 130 me.hasClass = function (e, c) { 131 var re = new RegExp("(^|\\s)" + c + "(\\s|$)"); 132 return re.test(e.className); 133 }; 134 135 me.addClass = function (e, c) { 136 if ( me.hasClass(e, c) ) { 137 return; 138 } 139 140 var newclass = e.className.split(' '); 141 newclass.push(c); 142 e.className = newclass.join(' '); 143 }; 144 145 me.removeClass = function (e, c) { 146 if ( !me.hasClass(e, c) ) { 147 return; 148 } 149 150 var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g'); 151 e.className = e.className.replace(re, ' '); 152 }; 153 154 me.offset = function (el) { 155 var left = -el.offsetLeft, 156 top = -el.offsetTop; 157 158 // jshint -W084 159 while (el = el.offsetParent) { 160 left -= el.offsetLeft; 161 top -= el.offsetTop; 162 } 163 // jshint +W084 164 165 return { 166 left: left, 167 top: top 168 }; 169 }; 170 171 me.preventDefaultException = function (el, exceptions) { 172 for ( var i in exceptions ) { 173 if ( exceptions[i].test(el[i]) ) { 174 return true; 175 } 176 } 177 178 return false;
179 }; 180 181 me.extend(me.eventType = {}, { 182 touchstart: 1, 183 touchmove: 1, 184 touchend: 1, 185 186 mousedown: 2, 187 mousemove: 2, 188 mouseup: 2, 189 190 pointerdown: 3, 191 pointermove: 3, 192 pointerup: 3, 193 194 MSPointerDown: 3, 195 MSPointerMove: 3, 196 MSPointerUp: 3 197 }); 198 199 me.extend(me.ease = {}, { 200 quadratic: { 201 style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', 202 fn: function (k) { 203 return k * ( 2 - k ); 204 } 205 }, 206 circular: { 207 style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) 208 fn: function (k) { 209 return Math.sqrt( 1 - ( --k * k ) ); 210 } 211 }, 212 back: { 213 style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', 214 fn: function (k) { 215 var b = 4; 216 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1; 217 } 218 }, 219 bounce: { 220 style: '', 221 fn: function (k) { 222 if ( ( k /= 1 ) < ( 1 / 2.75 ) ) { 223 return 7.5625 * k * k; 224 } else if ( k < ( 2 / 2.75 ) ) { 225 return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; 226 } else if ( k < ( 2.5 / 2.75 ) ) { 227 return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; 228 } else { 229 return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; 230 } 231 } 232 }, 233 elastic: { 234 style: '', 235 fn: function (k) { 236 var f = 0.22, 237 e = 0.4; 238 239 if ( k === 0 ) { return 0; } 240 if ( k == 1 ) { return 1; } 241 242 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 ); 243 } 244 } 245 }); 246 247 me.tap = function (e, eventName) { 248 var ev = document.createEvent('Event'); 249 ev.initEvent(eventName, true, true); 250 ev.pageX = e.pageX; 251 ev.pageY = e.pageY; 252 e.target.dispatchEvent(ev); 253 }; 254 255 me.click = function (e) { 256 var target = e.target, 257 ev; 258 259 if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) { 260 ev = document.createEvent('MouseEvents'); 261 ev.initMouseEvent('click', true, true, e.view, 1, 262 target.screenX, target.screenY, target.clientX, target.clientY, 263 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 264 0, null); 265 266 ev._constructed = true; 267 target.dispatchEvent(ev); 268 } 269 }; 270 271 return me; 272})(); 273function IScroll (el, options) { 274 this.wrapper = typeof el == 'string' ? document.querySelector(el) : el; 275 this.scroller = this.wrapper.children[0]; 276 this.scrollerStyle = this.scroller.style; // cache style for better performance 277 278 this.options = { 279 280 resizeScrollbars: true, 281 282 mouseWheelSpeed: 20, 283 284 snapThreshold: 0.334, 285 286// INSERT POINT: OPTIONS 287 disablePointer : !utils.hasPointer, 288 disableTouch : utils.hasPointer || !utils.hasTouch, 289 disableMouse : utils.hasPointer || utils.hasTouch, 290 startX: 0, 291 startY: 0, 292 scrollY: true, 293 directionLockThreshold: 5, 294 momentum: true, 295 296 bounce: true, 297 bounceTime: 600, 298 bounceEasing: '', 299 300 preventDefault: true, 301 preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }, 302 303 HWCompositing: true, 304 useTransition: true, 305 useTransform: true, 306 bindToWrapper: typeof window.onmousedown === "undefined" 307 }; 308 309 for ( var i in options ) { 310 this.options[i] = options[i]; 311 } 312 313 // Normalize options 314 this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : ''; 315 316 this.options.useTransition = utils.hasTransition && this.options.useTransition; 317 this.options.useTransform = utils.hasTransform && this.options.useTransform; 318 319 this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough; 320 this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault; 321 322 // If you want eventPassthrough I have to lock one of the axes 323 this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY; 324 this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX; 325 326 // With eventPassthrough we also need lockDirection mechanism 327 this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough; 328 this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold; 329 330 this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing; 331 332 this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling; 333 334 if ( this.options.tap === true ) {
335 this.options.tap = 'tap'; 336 } 337 338 if ( this.options.shrinkScrollbars == 'scale' ) { 339 this.options.useTransition = false; 340 } 341 342 this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1; 343 344// INSERT POINT: NORMALIZATION 345 346 // Some defaults 347 this.x = 0; 348 this.y = 0; 349 this.directionX = 0; 350 this.directionY = 0; 351 this._events = {}; 352 353// INSERT POINT: DEFAULTS 354 355 this._init(); 356 this.refresh(); 357 358 this.scrollTo(this.options.startX, this.options.startY); 359 this.enable(); 360} 361 362IScroll.prototype = { 363 version: '5.2.0', 364 365 _init: function () { 366 this._initEvents(); 367 368 if ( this.options.scrollbars || this.options.indicators ) { 369 this._initIndicators(); 370 } 371 372 if ( this.options.mouseWheel ) { 373 this._initWheel(); 374 } 375 376 if ( this.options.snap ) { 377 this._initSnap(); 378 } 379 380 if ( this.options.keyBindings ) { 381 this._initKeys(); 382 } 383 384// INSERT POINT: _init 385 386 }, 387 388 destroy: function () { 389 this._initEvents(true); 390 clearTimeout(this.resizeTimeout); 391 this.resizeTimeout = null; 392 this._execEvent('destroy'); 393 }, 394 395 _transitionEnd: function (e) { 396 if ( e.target != this.scroller || !this.isInTransition ) { 397 return; 398 } 399 400 this._transitionTime(); 401 if ( !this.resetPosition(this.options.bounceTime) ) { 402 this.isInTransition = false; 403 this._execEvent('scrollEnd'); 404 } 405 }, 406 407 _start: function (e) { 408 // React to left mouse button only 409 if ( utils.eventType[e.type] != 1 ) { 410 // for button property 411 // http://unixpapa.com/js/mouse.html 412 var button; 413 if (!e.which) { 414 /* IE case */ 415 button = (e.button < 2) ? 0 : 416 ((e.button == 4) ? 1 : 2); 417 } else { 418 /* All others */ 419 button = e.button; 420 } 421 if ( button !== 0 ) { 422 return; 423 } 424 } 425 426 if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) { 427 return; 428 } 429 430 if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { 431 e.preventDefault(); 432 } 433 434 var point = e.touches ? e.touches[0] : e, 435 pos; 436 437 this.initiated = utils.eventType[e.type]; 438 this.moved = false; 439 this.distX = 0; 440 this.distY = 0; 441 this.directionX = 0; 442 this.directionY = 0; 443 this.directionLocked = 0; 444 445 this.startTime = utils.getTime(); 446 447 if ( this.options.useTransition && this.isInTransition ) { 448 this._transitionTime(); 449 this.isInTransition = false; 450 pos = this.getComputedPosition(); 451 this._translate(Math.round(pos.x), Math.round(pos.y)); 452 this._execEvent('scrollEnd'); 453 } else if ( !this.options.useTransition && this.isAnimating ) { 454 this.isAnimating = false; 455 this._execEvent('scrollEnd'); 456 } 457 458 this.startX = this.x; 459 this.startY = this.y; 460 this.absStartX = this.x; 461 this.absStartY = this.y; 462 this.pointX = point.pageX; 463 this.pointY = point.pageY; 464 465 this._execEvent('beforeScrollStart'); 466 }, 467 468 _move: function (e) { 469 if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { 470 return; 471 } 472 473 if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! 474 e.preventDefault(); 475 } 476 477 var point = e.touches ? e.touches[0] : e, 478 deltaX = point.pageX - this.pointX, 479 deltaY = point.pageY - this.pointY, 480 timestamp = utils.getTime(), 481 newX, newY, 482 absDistX, absDistY; 483 484 this.pointX = point.pageX; 485 this.pointY = point.pageY; 486 487 this.distX += deltaX; 488 this.distY += deltaY; 489 absDistX = Math.abs(this.distX); 490 absDistY = Math.abs(this.distY); 491 492 // We need to move at least 10 pixels for the scrolling to initiate 493 if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) { 494 return; 495 } 496 497 // If you are scrolling in one direction lock the other 498 if ( !this.directionLocked && !this.options.freeScroll ) { 499 if ( absDistX > absDistY + this.options.directionLockThreshold ) { 500 this.directionLocked = 'h'; // lock horizontally 501 } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) { 502 this.directionLocked = 'v'; // lock vertically 503 } else { 504 this.directionLocked = 'n'; // no lock 505 } 506 } 507
508 if ( this.directionLocked == 'h' ) { 509 if ( this.options.eventPassthrough == 'vertical' ) { 510 e.preventDefault(); 511 } else if ( this.options.eventPassthrough == 'horizontal' ) { 512 this.initiated = false; 513 return; 514 } 515 516 deltaY = 0; 517 } else if ( this.directionLocked == 'v' ) { 518 if ( this.options.eventPassthrough == 'horizontal' ) { 519 e.preventDefault(); 520 } else if ( this.options.eventPassthrough == 'vertical' ) { 521 this.initiated = false; 522 return; 523 } 524 525 deltaX = 0; 526 } 527 528 deltaX = this.hasHorizontalScroll ? deltaX : 0; 529 deltaY = this.hasVerticalScroll ? deltaY : 0; 530 531 newX = this.x + deltaX; 532 newY = this.y + deltaY; 533 534 // Slow down if outside of the boundaries 535 if ( newX > 0 || newX < this.maxScrollX ) { 536 newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; 537 } 538 if ( newY > 0 || newY < this.maxScrollY ) { 539 newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; 540 } 541 542 this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; 543 this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; 544 545 if ( !this.moved ) { 546 this._execEvent('scrollStart'); 547 } 548 549 this.moved = true; 550 551 this._translate(newX, newY); 552 553/* REPLACE START: _move */ 554 555 if ( timestamp - this.startTime > 300 ) { 556 this.startTime = timestamp; 557 this.startX = this.x; 558 this.startY = this.y; 559 } 560 561/* REPLACE END: _move */ 562 563 }, 564 565 _end: function (e) { 566 if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { 567 return; 568 } 569 570 if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { 571 e.preventDefault(); 572 } 573 574 var point = e.changedTouches ? e.changedTouches[0] : e, 575 momentumX, 576 momentumY, 577 duration = utils.getTime() - this.startTime, 578 newX = Math.round(this.x), 579 newY = Math.round(this.y), 580 distanceX = Math.abs(newX - this.startX), 581 distanceY = Math.abs(newY - this.startY), 582 time = 0, 583 easing = ''; 584 585 this.isInTransition = 0; 586 this.initiated = 0; 587 this.endTime = utils.getTime(); 588 589 // reset if we are outside of the boundaries 590 if ( this.resetPosition(this.options.bounceTime) ) { 591 return; 592 } 593 594 this.scrollTo(newX, newY); // ensures that the last position is rounded 595 596 // we scrolled less than 10 pixels 597 if ( !this.moved ) { 598 if ( this.options.tap ) { 599 utils.tap(e, this.options.tap); 600 } 601 602 if ( this.options.click ) { 603 utils.click(e); 604 } 605 606 this._execEvent('scrollCancel'); 607 return; 608 } 609 610 if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) { 611 this._execEvent('flick'); 612 return; 613 } 614 615 // start momentum animation if needed 616 if ( this.options.momentum && duration < 300 ) { 617 momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 }; 618 momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 }; 619 newX = momentumX.destination; 620 newY = momentumY.destination; 621 time = Math.max(momentumX.duration, momentumY.duration); 622 this.isInTransition = 1; 623 } 624 625 626 if ( this.options.snap ) { 627 var snap = this._nearestSnap(newX, newY); 628 this.currentPage = snap; 629 time = this.options.snapSpeed || Math.max( 630 Math.max( 631 Math.min(Math.abs(newX - snap.x), 1000), 632 Math.min(Math.abs(newY - snap.y), 1000) 633 ), 300); 634 newX = snap.x; 635 newY = snap.y; 636 637 this.directionX = 0; 638 this.directionY = 0; 639 easing = this.options.bounceEasing; 640 } 641 642// INSERT POINT: _end 643 644 if ( newX != this.x || newY != this.y ) { 645 // change easing function when scroller goes out of the boundaries 646 if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) { 647 easing = utils.ease.quadratic; 648 } 649 650 this.scrollTo(newX, newY, time, easing); 651 return; 652 } 653 654 this._execEvent('scrollEnd'); 655 }, 656 657 _resize: function () { 658 var that = this; 659 660 clearTimeout(this.resizeTimeout); 661 662 this.resizeTimeout = setTimeout(function () { 663 that.refresh(); 664 }, this.options.resizePolling); 665 }, 666 667 resetPosition: function (time) { 668 var x = this.x, 669 y = this.y; 670 671 time = time || 0; 672 673 if ( !this.hasHorizontalScroll || this.x > 0 ) { 674 x = 0; 675 } else if ( this.x < this.maxScrollX ) { 676 x = this.maxScrollX; 677 } 678 679 if ( !this.hasVerticalScroll || this.y > 0 ) { 680 y = 0; 681 } else if ( this.y < this.maxScrollY ) { 682 y = this.maxScrollY; 683 } 684 685 if ( x == this.x && y == this.y ) { 686 return false;
687 } 688 689 this.scrollTo(x, y, time, this.options.bounceEasing); 690 691 return true; 692 }, 693 694 disable: function () { 695 this.enabled = false; 696 }, 697 698 enable: function () { 699 this.enabled = true; 700 }, 701 702 refresh: function () { 703 var rf = this.wrapper.offsetHeight; // Force reflow 704 705 this.wrapperWidth = this.wrapper.clientWidth; 706 this.wrapperHeight = this.wrapper.clientHeight; 707 708/* REPLACE START: refresh */ 709 710 this.scrollerWidth = this.scroller.offsetWidth; 711 this.scrollerHeight = this.scroller.offsetHeight; 712 713 this.maxScrollX = this.wrapperWidth - this.scrollerWidth; 714 this.maxScrollY = this.wrapperHeight - this.scrollerHeight; 715 716/* REPLACE END: refresh */ 717 718 this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0; 719 this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0; 720 721 if ( !this.hasHorizontalScroll ) { 722 this.maxScrollX = 0; 723 this.scrollerWidth = this.wrapperWidth; 724 } 725 726 if ( !this.hasVerticalScroll ) { 727 this.maxScrollY = 0; 728 this.scrollerHeight = this.wrapperHeight; 729 } 730 731 this.endTime = 0; 732 this.directionX = 0; 733 this.directionY = 0; 734 735 this.wrapperOffset = utils.offset(this.wrapper); 736 737 this._execEvent('refresh'); 738 739 this.resetPosition(); 740 741// INSERT POINT: _refresh 742 743 }, 744 745 on: function (type, fn) { 746 if ( !this._events[type] ) { 747 this._events[type] = []; 748 } 749 750 this._events[type].push(fn); 751 }, 752 753 off: function (type, fn) { 754 if ( !this._events[type] ) { 755 return; 756 } 757 758 var index = this._events[type].indexOf(fn); 759 760 if ( index > -1 ) { 761 this._events[type].splice(index, 1); 762 } 763 }, 764 765 _execEvent: function (type) { 766 if ( !this._events[type] ) { 767 return; 768 } 769 770 var i = 0, 771 l = this._events[type].length; 772 773 if ( !l ) { 774 return; 775 } 776 777 for ( ; i < l; i++ ) { 778 this._events[type][i].apply(this, [].slice.call(arguments, 1)); 779 } 780 }, 781 782 scrollBy: function (x, y, time, easing) { 783 x = this.x + x; 784 y = this.y + y; 785 time = time || 0; 786 787 this.scrollTo(x, y, time, easing); 788 }, 789 790 scrollTo: function (x, y, time, easing) { 791 easing = easing || utils.ease.circular; 792 793 this.isInTransition = this.options.useTransition && time > 0; 794 var transitionType = this.options.useTransition && easing.style; 795 if ( !time || transitionType ) { 796 if(transitionType) { 797 this._transitionTimingFunction(easing.style); 798 this._transitionTime(time); 799 } 800 this._translate(x, y); 801 } else { 802 this._animate(x, y, time, easing.fn); 803 } 804 }, 805 806 scrollToElement: function (el, time, offsetX, offsetY, easing) { 807 el = el.nodeType ? el : this.scroller.querySelector(el); 808 809 if ( !el ) { 810 return; 811 } 812 813 var pos = utils.offset(el); 814 815 pos.left -= this.wrapperOffset.left; 816 pos.top -= this.wrapperOffset.top; 817 818 // if offsetX/Y are true we center the element to the screen 819 if ( offsetX === true ) { 820 offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2); 821 } 822 if ( offsetY === true ) { 823 offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2); 824 } 825 826 pos.left -= offsetX || 0; 827 pos.top -= offsetY || 0; 828 829 pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left; 830 pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top; 831 832 time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time; 833 834 this.scrollTo(pos.left, pos.top, time, easing); 835 }, 836 837 _transitionTime: function (time) { 838 time = time || 0; 839 840 var durationProp = utils.style.transitionDuration; 841 this.scrollerStyle[durationProp] = time + 'ms'; 842 843 if ( !time && utils.isBadAndroid ) { 844 this.scrollerStyle[durationProp] = '0.0001ms'; 845 // remove 0.0001ms 846 var self = this; 847 rAF(function() { 848 if(self.scrollerStyle[durationProp] === '0.0001ms') { 849 self.scrollerStyle[durationProp] = '0s'; 850 } 851 }); 852 } 853 854 855 if ( this.indicators ) { 856 for ( var i = this.indicators.length; i--; ) { 857 this.indicators[i].transitionTime(time); 858 } 859 } 860 861 862// INSERT POINT: _transitionTime 863 864 }, 865 866 _transitionTimingFunction: function (easing) { 867 this.scrollerStyle[utils.style.transitionTimingFunction] = easing; 868 869 870 if ( this.indicators ) { 871 for ( var i = this.indicators.length; i--; ) { 872 this.indicators[i].transitionTimingFunction(easing); 873 } 874 } 875 876 877// INSERT POINT: _transitionTimingFunction 878 879 }, 880 881 _translate: function (x, y) { 882 if ( this.options.useTransform ) { 883 884/* REPLACE START: _translate */ 885 886 this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 887 888/* REPLACE END: _translate */ 889 890 } else { 891 x = Math.round(x); 892 y = Math.round(y); 893 this.scrollerStyle.left = x + 'px'; 894 this.scrollerStyle.top = y + 'px'; 895 } 896 897 this.x = x; 898 this.y = y; 899 900 901 if ( this.indicators ) { 902 for ( var i = this.indicators.length; i--; ) { 903 this.indicators[i].updatePosition(); 904 } 905 } 906 907 908// INSERT POINT: _translate 909 910 }, 911 912 _initEvents: function (remove) { 913 var eventType = remove ? utils.removeEvent : utils.addEvent, 914 target = this.options.bindToWrapper ? this.wrapper : window; 915 916 eventType(window, 'orientationchange', this); 917 eventType(window, 'resize', this); 918 919 if ( this.options.click ) { 920 eventType(this.wrapper, 'click', this, true); 921 } 922 923 if ( !this.options.disableMouse ) { 924 eventType(this.wrapper, 'mousedown', this); 925 eventType(target, 'mousemove', this); 926 eventType(target, 'mousecancel', this); 927 eventType(target, 'mouseup', this); 928 } 929 930 if ( utils.hasPointer && !this.options.disablePointer ) { 931 eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this); 932 eventType(target, utils.prefixPointerEvent('pointermove'), this); 933 eventType(target, utils.prefixPointerEvent('pointercancel'), this); 934 eventType(target, utils.prefixPointerEvent('pointerup'), this); 935 } 936 937 if ( utils.hasTouch && !this.options.disableTouch ) { 938 eventType(this.wrapper, 'touchstart', this); 939 eventType(target, 'touchmove', this); 940 eventType(target, 'touchcancel', this); 941 eventType(target, 'touchend', this); 942 } 943 944 eventType(this.scroller, 'transitionend', this); 945 eventType(this.scroller, 'webkitTransitionEnd', this); 946 eventType(this.scroller, 'oTransitionEnd', this); 947 eventType(this.scroller, 'MSTransitionEnd', this); 948 }, 949 950 getComputedPosition: function () { 951 var matrix = window.getComputedStyle(this.scroller, null), 952 x, y; 953 954 if ( this.options.useTransform ) { 955 matrix = matrix[utils.style.transform].split(')')[0].split(', '); 956 x = +(matrix[12] || matrix[4]); 957 y = +(matrix[13] || matrix[5]); 958 } else { 959 x = +matrix.left.replace(/[^-\d.]/g, ''); 960 y = +matrix.top.replace(/[^-\d.]/g, ''); 961 } 962 963 return { x: x, y: y }; 964 }, 965 _initIndicators: function () { 966 var interactive = this.options.interactiveScrollbars, 967 customStyle = typeof this.options.scrollbars != 'string', 968 indicators = [], 969 indicator; 970 971 var that = this; 972 973 this.indicators = []; 974 975 if ( this.options.scrollbars ) { 976 // Vertical scrollbar 977 if ( this.options.scrollY ) { 978 indicator = { 979 el: createDefaultScrollbar('v', interactive, this.options.scrollbars), 980 interactive: interactive, 981 defaultScrollbars: true, 982 customStyle: customStyle, 983 resize: this.options.resizeScrollbars, 984 shrink: this.options.shrinkScrollbars, 985 fade: this.options.fadeScrollbars, 986 listenX: false 987 }; 988 989 this.wrapper.appendChild(indicator.el); 990 indicators.push(indicator); 991 } 992 993 // Horizontal scrollbar 994 if ( this.options.scrollX ) { 995 indicator = { 996 el: createDefaultScrollbar('h', interactive, this.options.scrollbars), 997 interactive: interactive, 998 defaultScrollbars: true, 999 customStyle: customStyle, 1000 resize: this.options.resizeScrollbars, 1001 shrink: this.options.shrinkScrollbars, 1002 fade: this.options.fadeScrollbars, 1003 listenY: false 1004 }; 1005 1006 this.wrapper.appendChild(indicator.el); 1007 indicators.push(indicator); 1008 } 1009 } 1010 1011 if ( this.options.indicators ) { 1012 // TODO: check concat compatibility 1013 indicators = indicators.concat(this.options.indicators); 1014 } 1015 1016 for ( var i = indicators.length; i--; ) { 1017 this.indicators.push( new Indicator(this, indicators[i]) ); 1018 } 1019 1020 // TODO: check if we can use array.map (wide compatibility and performance issues) 1021 function _indicatorsMap (fn) { 1022 if (that.indicators) { 1023 for ( var i = that.indicators.length; i--; ) { 1024 fn.call(that.indicators[i]); 1025 } 1026 } 1027 } 1028 1029 if ( this.options.fadeScrollbars ) { 1030 this.on('scrollEnd', function () { 1031 _indicatorsMap(function () { 1032 this.fade(); 1033 }); 1034 }); 1035 1036 this.on('scrollCancel', function () { 1037 _indicatorsMap(function () { 1038 this.fade(); 1039 }); 1040 }); 1041 1042 this.on('scrollStart', function () { 1043 _indicatorsMap(function () { 1044 this.fade(1); 1045 }); 1046 }); 1047 1048 this.on('beforeScrollStart', function () { 1049 _indicatorsMap(function () { 1050 this.fade(1, true); 1051 }); 1052 }); 1053 } 1054 1055 1056 this.on('refresh', function () { 1057 _indicatorsMap(function () { 1058 this.refresh(); 1059 }); 1060 }); 1061 1062 this.on('destroy', function () { 1063 _indicatorsMap(function () { 1064 this.destroy(); 1065 }); 1066 1067 delete this.indicators; 1068 }); 1069 }, 1070 1071 _initWheel: function () { 1072 utils.addEvent(this.wrapper, 'wheel', this); 1073 utils.addEvent(this.wrapper, 'mousewheel', this); 1074 utils.addEvent(this.wrapper, 'DOMMouseScroll', this); 1075 1076 this.on('destroy', function () { 1077 clearTimeout(this.wheelTimeout); 1078 this.wheelTimeout = null; 1079 utils.removeEvent(this.wrapper, 'wheel', this); 1080 utils.removeEvent(this.wrapper, 'mousewheel', this); 1081 utils.removeEvent(this.wrapper, 'DOMMouseScroll', this); 1082 }); 1083 }, 1084 1085 _wheel: function (e) { 1086 if ( !this.enabled ) { 1087 return; 1088 } 1089 1090 e.preventDefault(); 1091 1092 var wheelDeltaX, wheelDeltaY, 1093 newX, newY, 1094 that = this; 1095 1096 if ( this.wheelTimeout === undefined ) { 1097 that._execEvent('scrollStart'); 1098 } 1099 1100 // Execute the scrollEnd event after 400ms the wheel stopped scrolling 1101 clearTimeout(this.wheelTimeout); 1102 this.wheelTimeout = setTimeout(function () { 1103 if(!that.options.snap) { 1104 that._execEvent('scrollEnd'); 1105 } 1106 that.wheelTimeout = undefined; 1107 }, 400); 1108 1109 if ( 'deltaX' in e ) { 1110 if (e.deltaMode === 1) { 1111 wheelDeltaX = -e.deltaX * this.options.mouseWheelSpeed; 1112 wheelDeltaY = -e.deltaY * this.options.mouseWheelSpeed; 1113 } else { 1114 wheelDeltaX = -e.deltaX; 1115 wheelDeltaY = -e.deltaY; 1116 } 1117 } else if ( 'wheelDeltaX' in e ) { 1118 wheelDeltaX = e.wheelDeltaX / 120 * this.options.mouseWheelSpeed; 1119 wheelDeltaY = e.wheelDeltaY / 120 * this.options.mouseWheelSpeed; 1120 } else if ( 'wheelDelta' in e ) { 1121 wheelDeltaX = wheelDeltaY = e.wheelDelta / 120 * this.options.mouseWheelSpeed; 1122 } else if ( 'detail' in e ) { 1123 wheelDeltaX = wheelDeltaY = -e.detail / 3 * this.options.mouseWheelSpeed; 1124 } else { 1125 return; 1126 } 1127 1128 wheelDeltaX *= this.options.invertWheelDirection; 1129 wheelDeltaY *= this.options.invertWheelDirection; 1130 1131 if ( !this.hasVerticalScroll ) { 1132 wheelDeltaX = wheelDeltaY; 1133 wheelDeltaY = 0; 1134 } 1135 1136 if ( this.options.snap ) { 1137 newX = this.currentPage.pageX; 1138 newY = this.currentPage.pageY; 1139 1140 if ( wheelDeltaX > 0 ) { 1141 newX--; 1142 } else if ( wheelDeltaX < 0 ) { 1143 newX++; 1144 } 1145 1146 if ( wheelDeltaY > 0 ) { 1147 newY--; 1148 } else if ( wheelDeltaY < 0 ) { 1149 newY++; 1150 } 1151 1152 this.goToPage(newX, newY); 1153 1154 return; 1155 } 1156 1157 newX = this.x + Math.round(this.hasHorizontalScroll ? wheelDeltaX : 0); 1158 newY = this.y + Math.round(this.hasVerticalScroll ? wheelDeltaY : 0); 1159 1160 this.directionX = wheelDeltaX > 0 ? -1 : wheelDeltaX < 0 ? 1 : 0; 1161 this.directionY = wheelDeltaY > 0 ? -1 : wheelDeltaY < 0 ? 1 : 0; 1162 1163 if ( newX > 0 ) { 1164 newX = 0; 1165 } else if ( newX < this.maxScrollX ) { 1166 newX = this.maxScrollX; 1167 } 1168 1169 if ( newY > 0 ) { 1170 newY = 0; 1171 } else if ( newY < this.maxScrollY ) { 1172 newY = this.maxScrollY; 1173 } 1174 1175 this.scrollTo(newX, newY, 0); 1176 1177// INSERT POINT: _wheel 1178 }, 1179 1180 _initSnap: function () { 1181 this.currentPage = {}; 1182 1183 if ( typeof this.options.snap == 'string' ) { 1184 this.options.snap = this.scroller.querySelectorAll(this.options.snap); 1185 } 1186 1187 this.on('refresh', function () { 1188 var i = 0, l, 1189 m = 0, n, 1190 cx, cy, 1191 x = 0, y,
1192 stepX = this.options.snapStepX || this.wrapperWidth, 1193 stepY = this.options.snapStepY || this.wrapperHeight, 1194 el; 1195 1196 this.pages = []; 1197 1198 if ( !this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight ) { 1199 return; 1200 } 1201 1202 if ( this.options.snap === true ) { 1203 cx = Math.round( stepX / 2 ); 1204 cy = Math.round( stepY / 2 ); 1205 1206 while ( x > -this.scrollerWidth ) { 1207 this.pages[i] = []; 1208 l = 0; 1209 y = 0; 1210 1211 while ( y > -this.scrollerHeight ) { 1212 this.pages[i][l] = { 1213 x: Math.max(x, this.maxScrollX), 1214 y: Math.max(y, this.maxScrollY), 1215 width: stepX, 1216 height: stepY, 1217 cx: x - cx, 1218 cy: y - cy 1219 }; 1220 1221 y -= stepY; 1222 l++; 1223 } 1224 1225 x -= stepX; 1226 i++; 1227 } 1228 } else { 1229 el = this.options.snap; 1230 l = el.length; 1231 n = -1; 1232 1233 for ( ; i < l; i++ ) { 1234 if ( i === 0 || el[i].offsetLeft <= el[i-1].offsetLeft ) { 1235 m = 0; 1236 n++; 1237 } 1238 1239 if ( !this.pages[m] ) { 1240 this.pages[m] = []; 1241 } 1242 1243 x = Math.max(-el[i].offsetLeft, this.maxScrollX); 1244 y = Math.max(-el[i].offsetTop, this.maxScrollY); 1245 cx = x - Math.round(el[i].offsetWidth / 2); 1246 cy = y - Math.round(el[i].offsetHeight / 2); 1247 1248 this.pages[m][n] = { 1249 x: x, 1250 y: y, 1251 width: el[i].offsetWidth, 1252 height: el[i].offsetHeight, 1253 cx: cx, 1254 cy: cy 1255 }; 1256 1257 if ( x > this.maxScrollX ) { 1258 m++; 1259 } 1260 } 1261 } 1262 1263 this.goToPage(this.currentPage.pageX || 0, this.currentPage.pageY || 0, 0); 1264 1265 // Update snap threshold if needed 1266 if ( this.options.snapThreshold % 1 === 0 ) { 1267 this.snapThresholdX = this.options.snapThreshold; 1268 this.snapThresholdY = this.options.snapThreshold; 1269 } else { 1270 this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * this.options.snapThreshold); 1271 this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * this.options.snapThreshold); 1272 } 1273 }); 1274 1275 this.on('flick', function () { 1276 var time = this.options.snapSpeed || Math.max( 1277 Math.max( 1278 Math.min(Math.abs(this.x - this.startX), 1000), 1279 Math.min(Math.abs(this.y - this.startY), 1000) 1280 ), 300); 1281 1282 this.goToPage( 1283 this.currentPage.pageX + this.directionX, 1284 this.currentPage.pageY + this.directionY, 1285 time 1286 ); 1287 }); 1288 }, 1289 1290 _nearestSnap: function (x, y) { 1291 if ( !this.pages.length ) { 1292 return { x: 0, y: 0, pageX: 0, pageY: 0 }; 1293 } 1294 1295 var i = 0, 1296 l = this.pages.length, 1297 m = 0; 1298 1299 // Check if we exceeded the snap threshold 1300 if ( Math.abs(x - this.absStartX) < this.snapThresholdX && 1301 Math.abs(y - this.absStartY) < this.snapThresholdY ) { 1302 return this.currentPage; 1303 } 1304 1305 if ( x > 0 ) { 1306 x = 0; 1307 } else if ( x < this.maxScrollX ) { 1308 x = this.maxScrollX; 1309 } 1310 1311 if ( y > 0 ) { 1312 y = 0; 1313 } else if ( y < this.maxScrollY ) { 1314 y = this.maxScrollY; 1315 } 1316 1317 for ( ; i < l; i++ ) { 1318 if ( x >= this.pages[i][0].cx ) { 1319 x = this.pages[i][0].x; 1320 break; 1321 } 1322 } 1323 1324 l = this.pages[i].length; 1325 1326 for ( ; m < l; m++ ) { 1327 if ( y >= this.pages[0][m].cy ) { 1328 y = this.pages[0][m].y; 1329 break; 1330 } 1331 } 1332 1333 if ( i == this.currentPage.pageX ) { 1334 i += this.directionX; 1335 1336 if ( i < 0 ) { 1337 i = 0; 1338 } else if ( i >= this.pages.length ) { 1339 i = this.pages.length - 1; 1340 } 1341 1342 x = this.pages[i][0].x; 1343 } 1344 1345 if ( m == this.currentPage.pageY ) { 1346 m += this.directionY; 1347 1348 if ( m < 0 ) { 1349 m = 0; 1350 } else if ( m >= this.pages[0].length ) { 1351 m = this.pages[0].length - 1; 1352 } 1353 1354 y = this.pages[0][m].y; 1355 } 1356 1357 return { 1358 x: x, 1359 y: y, 1360 pageX: i, 1361 pageY: m 1362 }; 1363 }, 1364 1365 goToPage: function (x, y, time, easing) { 1366 easing = easing || this.options.bounceEasing; 1367 1368 if ( x >= this.pages.length ) { 1369 x = this.pages.length - 1; 1370 } else if ( x < 0 ) { 1371 x = 0; 1372 } 1373 1374 if ( y >= this.pages[x].length ) { 1375 y = this.pages[x].length - 1; 1376 } else if ( y < 0 ) { 1377 y = 0; 1378 } 1379 1380 var posX = this.pages[x][y].x, 1381 posY = this.pages[x][y].y; 1382 1383 time = time === undefined ? this.options.snapSpeed || Math.max( 1384 Math.max( 1385 Math.min(Math.abs(posX - this.x), 1000), 1386 Math.min(Math.abs(posY - this.y), 1000) 1387 ), 300) : time; 1388 1389 this.currentPage = { 1390 x: posX, 1391 y: posY, 1392 pageX: x, 1393 pageY: y 1394 }; 1395 1396 this.scrollTo(posX, posY, time, easing); 1397 }, 1398 1399 next: function (time, easing) { 1400 var x = this.currentPage.pageX, 1401 y = this.currentPage.pageY; 1402 1403 x++; 1404 1405 if ( x >= this.pages.length && this.hasVerticalScroll ) { 1406 x = 0; 1407 y++; 1408 } 1409 1410 this.goToPage(x, y, time, easing); 1411 }, 1412 1413 prev: function (time, easing) { 1414 var x = this.currentPage.pageX, 1415 y = this.currentPage.pageY; 1416 1417 x--; 1418 1419 if ( x < 0 && this.hasVerticalScroll ) { 1420 x = 0; 1421 y--; 1422 } 1423 1424 this.goToPage(x, y, time, easing); 1425 }, 1426 1427 _initKeys: function (e) { 1428 // default key bindings 1429 var keys = { 1430 pageUp: 33, 1431 pageDown: 34, 1432 end: 35, 1433 home: 36, 1434 left: 37, 1435 up: 38, 1436 right: 39, 1437 down: 40 1438 }; 1439 var i; 1440 1441 // if you give me characters I give you keycode 1442 if ( typeof this.options.keyBindings == 'object' ) { 1443 for ( i in this.options.keyBindings ) { 1444 if ( typeof this.options.keyBindings[i] == 'string' ) { 1445 this.options.keyBindings[i] = this.options.keyBindings[i].toUpperCase().charCo
1445deAt(0); 1446 } 1447 } 1448 } else { 1449 this.options.keyBindings = {}; 1450 } 1451 1452 for ( i in keys ) { 1453 this.options.keyBindings[i] = this.options.keyBindings[i] || keys[i]; 1454 } 1455 1456 utils.addEvent(window, 'keydown', this); 1457 1458 this.on('destroy', function () { 1459 utils.removeEvent(window, 'keydown', this); 1460 }); 1461 }, 1462 1463 _key: function (e) { 1464 if ( !this.enabled ) { 1465 return; 1466 } 1467 1468 var snap = this.options.snap, // we are using this alot, better to cache it 1469 newX = snap ? this.currentPage.pageX : this.x, 1470 newY = snap ? this.currentPage.pageY : this.y, 1471 now = utils.getTime(), 1472 prevTime = this.keyTime || 0, 1473 acceleration = 0.250, 1474 pos; 1475 1476 if ( this.options.useTransition && this.isInTransition ) { 1477 pos = this.getComputedPosition(); 1478 1479 this._translate(Math.round(pos.x), Math.round(pos.y)); 1480 this.isInTransition = false; 1481 } 1482 1483 this.keyAcceleration = now - prevTime < 200 ? Math.min(this.keyAcceleration + acceleration, 50) : 0; 1484 1485 switch ( e.keyCode ) { 1486 case this.options.keyBindings.pageUp: 1487 if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { 1488 newX += snap ? 1 : this.wrapperWidth; 1489 } else { 1490 newY += snap ? 1 : this.wrapperHeight; 1491 } 1492 break; 1493 case this.options.keyBindings.pageDown: 1494 if ( this.hasHorizontalScroll && !this.hasVerticalScroll ) { 1495 newX -= snap ? 1 : this.wrapperWidth; 1496 } else { 1497 newY -= snap ? 1 : this.wrapperHeight; 1498 } 1499 break; 1500 case this.options.keyBindings.end: 1501 newX = snap ? this.pages.length-1 : this.maxScrollX; 1502 newY = snap ? this.pages[0].length-1 : this.maxScrollY; 1503 break; 1504 case this.options.keyBindings.home: 1505 newX = 0; 1506 newY = 0; 1507 break; 1508 case this.options.keyBindings.left: 1509 newX += snap ? -1 : 5 + this.keyAcceleration>>0; 1510 break; 1511 case this.options.keyBindings.up: 1512 newY += snap ? 1 : 5 + this.keyAcceleration>>0; 1513 break; 1514 case this.options.keyBindings.right: 1515 newX -= snap ? -1 : 5 + this.keyAcceleration>>0; 1516 break; 1517 case this.options.keyBindings.down: 1518 newY -= snap ? 1 : 5 + this.keyAcceleration>>0; 1519 break; 1520 default: 1521 return; 1522 } 1523 1524 if ( snap ) { 1525 this.goToPage(newX, newY); 1526 return; 1527 } 1528 1529 if ( newX > 0 ) { 1530 newX = 0; 1531 this.keyAcceleration = 0; 1532 } else if ( newX < this.maxScrollX ) { 1533 newX = this.maxScrollX; 1534 this.keyAcceleration = 0; 1535 } 1536 1537 if ( newY > 0 ) { 1538 newY = 0; 1539 this.keyAcceleration = 0; 1540 } else if ( newY < this.maxScrollY ) { 1541 newY = this.maxScrollY; 1542 this.keyAcceleration = 0; 1543 } 1544 1545 this.scrollTo(newX, newY, 0); 1546 1547 this.keyTime = now; 1548 }, 1549 1550 _animate: function (destX, destY, duration, easingFn) { 1551 var that = this, 1552 startX = this.x, 1553 startY = this.y, 1554 startTime = utils.getTime(), 1555 destTime = startTime + duration; 1556 1557 function step () { 1558 var now = utils.getTime(), 1559 newX, newY, 1560 easing; 1561 1562 if ( now >= destTime ) { 1563 that.isAnimating = false; 1564 that._translate(destX, destY); 1565 1566 if ( !that.resetPosition(that.options.bounceTime) ) { 1567 that._execEvent('scrollEnd'); 1568 } 1569 1570 return; 1571 } 1572 1573 now = ( now - startTime ) / duration; 1574 easing = easingFn(now); 1575 newX = ( destX - startX ) * easing + startX; 1576 newY = ( destY - startY ) * easing + startY; 1577 that._translate(newX, newY); 1578 1579 if ( that.isAnimating ) { 1580 rAF(step); 1581 } 1582 } 1583 1584 this.isAnimating = true; 1585 step(); 1586 }, 1587 handleEvent: function (e) { 1588 switch ( e.type ) { 1589 case 'touchstart': 1590 case 'pointerdown': 1591 case 'MSPointerDown': 1592 case 'mousedown': 1593 this._start(e); 1594 break; 1595 case 'touchmove': 1596 case 'pointermove': 1597 case 'MSPointerMove': 1598 case 'mousemove': 1599 this._move(e); 1600 break; 1601 case 'touchend': 1602 case 'pointerup': 1603 case 'MSPointerUp': 1604 case 'mouseup': 1605 case 'touchcancel': 1606 case 'pointercancel': 1607 case 'MSPointerCancel': 1608 case 'mousecancel': 1609 this._end(e); 1610 break; 1611 case 'orientationchange': 1612 case 'resize': 1613 this._resize(); 1614 break; 1615 case 'transitionend': 1616 case 'webkitTransitionEnd': 1617 case 'oTransitionEnd': 1618 case 'MSTransitionEnd': 1619 this._transitionEnd(e); 1620 break; 1621 case 'wheel': 1622 case 'DOMMouseScroll': 1623 case 'mousewheel': 1624 this._wheel(e); 1625 break; 1626 case 'keydown': 1627 this._key(e); 1628 break; 1629 case 'click': 1630 if ( this.enabled && !e._constructed ) { 1631 e.preventDefault(); 1632 e.stopPropagation(); 1633 } 1634 break; 1635 } 1636 } 1637}; 1638function createDefaultScrollbar (direction, interactive, type) { 1639 var scrollbar = document.createElement('div'), 1640 indicator = document.createElement('div'); 1641 1642 if ( type === true ) { 1643 scrollbar.style.cssText = 'position:absolute;z-index:9999'; 1644 indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px'; 1645 } 1646 1647 indicator.className = 'iScrollIndicator'; 1648 1649 if ( direction == 'h' ) { 1650 if ( type === true ) { 1651 scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0'; 1652 indicator.style.height = '100%'; 1653 } 1654 scrollbar.className = 'iScrollHorizontalScrollbar'; 1655 } else { 1656 if ( type === true ) { 1657 scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px'; 1658 indicator.style.width = '100%'; 1659 } 1660 scrollbar.className = 'iScrollVerticalScrollbar'; 1661 } 1662 1663 scrollbar.style.cssText += ';overflow:hidden'; 1664 1665 if ( !interactive ) { 1666 scrollbar.style.pointerEvents = 'none'; 1667 } 1668 1669 scrollbar.appendChild(indicator); 1670 1671 return scrollbar; 1672} 1673 1674function Indicator (scroller, options) { 1675 this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el; 1676 this.wrapperStyle = this.wrapper.style; 1677 this.indicator = this.wrapper.children[0]; 1678 this.indicatorStyle = this.indicator.style; 1679 this.scroller = scroller; 1680 1681 this.options = { 1682 listenX: true, 1683 listenY: true, 1684 interactive: false, 1685 resize: true, 1686 defaultScrollbars: false, 1687 shrink: false, 1688 fade: false, 1689 speedRatioX: 0, 1690 speedRatioY: 0 1691 }; 1692 1693 for ( var i in options ) { 1694 this.options[i] = options[i]; 1695 } 1696 1697 this.sizeRatioX = 1; 1698 this.sizeRatioY = 1; 1699 this.maxPosX = 0; 1700 this.maxPosY = 0; 1701 1702 if ( this.options.interactive ) { 1703 if ( !this.options.disableTouch ) { 1704 utils.addEvent(this.indicator, 'touchstart', this); 1705 utils.addEvent(window, 'touchend', this); 1706 } 1707 if ( !this.options.disablePointer ) { 1708 utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); 1709 utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this); 1710 } 1711 if ( !this.options.disableMouse ) { 1712 utils.addEvent(this.indicator, 'mousedown', this); 1713 utils.addEvent(window, 'mouseup', this); 1714 } 1715 } 1716 1717 if ( this.options.fade ) { 1718 this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; 1719 var durationProp = utils.style.transitionDuration; 1720 this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms'; 1721 // remove 0.0001ms 1722 var self = this; 1723 if(utils.isBadAndroid) { 1724 rAF(function() { 1725 if(self.wrapperStyle[durationProp] === '0.0001ms') { 1726 self.wrapperStyle[durationProp] = '0s'; 1727 } 1728 }); 1729 } 1730 this.wrapperStyle.opacity = '0'; 1731 } 1732} 1733 1734Indicator.prototype = { 1735 handleEvent: function (e) { 1736 switch ( e.type ) { 1737 case 'touchstart': 1738 case 'pointerdown': 1739 case 'MSPointerDown': 1740 case 'mousedown': 1741 this._start(e); 1742 break; 1743 case 'touchmove': 1744 case 'pointermove': 1745 case 'MSPointerMove': 1746 case 'mousemove': 1747 this._move(e); 1748 break; 1749 case 'touchend': 1750 case 'pointerup': 1751 case 'MSPointerUp': 1752 case 'mouseup': 1753 case 'touchcancel': 1754 case 'pointercancel': 1755 case 'MSPointerCancel': 1756 case 'mousecancel': 1757 this._end(e); 1758 break; 1759 } 1760 }, 1761 1762 destroy: function () { 1763 if ( this.options.fadeScrollbars ) { 1764 clearTimeout(this.fadeTimeout); 1765 this.fadeTimeout = null; 1766 } 1767 if ( this.options.interactive ) { 1768 utils.removeEvent(this.indicator, 'touchstart', this); 1769 utils.removeEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); 1770 utils.removeEvent(this.indicator, 'mousedown', this); 1771 1772 utils.removeEvent(window, 'touchmove', this); 1773 utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); 1774 utils.removeEvent(window, 'mousemove', this); 1775 1776 utils.removeEvent(window, 'touchend', this); 1777 utils.removeEvent(window, utils.prefixPointerEvent('pointerup'), this); 1778 utils.removeEvent(window, 'mouseup', this); 1779 } 1780 1781 if ( this.options.defaultScrollbars ) { 1782 this.wrapper.parentNode.removeChild(this.wrapper); 1783 } 1784 }, 1785 1786 _start: function (e) { 1787 var point = e.touches ? e.touches[0] : e; 1788 1789 e.preventDefault(); 1790 e.stopPropagation(); 1791 1792 this.transitionTime(); 1793 1794 this.initiated = true; 1795 this.moved = false;
1796 this.lastPointX = point.pageX; 1797 this.lastPointY = point.pageY; 1798 1799 this.startTime = utils.getTime(); 1800 1801 if ( !this.options.disableTouch ) { 1802 utils.addEvent(window, 'touchmove', this); 1803 } 1804 if ( !this.options.disablePointer ) { 1805 utils.addEvent(window, utils.prefixPointerEvent('pointermove'), this); 1806 } 1807 if ( !this.options.disableMouse ) { 1808 utils.addEvent(window, 'mousemove', this); 1809 } 1810 1811 this.scroller._execEvent('beforeScrollStart'); 1812 }, 1813 1814 _move: function (e) { 1815 var point = e.touches ? e.touches[0] : e, 1816 deltaX, deltaY, 1817 newX, newY, 1818 timestamp = utils.getTime(); 1819 1820 if ( !this.moved ) { 1821 this.scroller._execEvent('scrollStart'); 1822 } 1823 1824 this.moved = true; 1825 1826 deltaX = point.pageX - this.lastPointX; 1827 this.lastPointX = point.pageX; 1828 1829 deltaY = point.pageY - this.lastPointY; 1830 this.lastPointY = point.pageY; 1831 1832 newX = this.x + deltaX; 1833 newY = this.y + deltaY; 1834 1835 this._pos(newX, newY); 1836 1837// INSERT POINT: indicator._move 1838 1839 e.preventDefault(); 1840 e.stopPropagation(); 1841 }, 1842 1843 _end: function (e) { 1844 if ( !this.initiated ) { 1845 return; 1846 } 1847 1848 this.initiated = false; 1849 1850 e.preventDefault(); 1851 e.stopPropagation(); 1852 1853 utils.removeEvent(window, 'touchmove', this); 1854 utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); 1855 utils.removeEvent(window, 'mousemove', this); 1856 1857 if ( this.scroller.options.snap ) { 1858 var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y); 1859 1860 var time = this.options.snapSpeed || Math.max( 1861 Math.max( 1862 Math.min(Math.abs(this.scroller.x - snap.x), 1000), 1863 Math.min(Math.abs(this.scroller.y - snap.y), 1000) 1864 ), 300); 1865 1866 if ( this.scroller.x != snap.x || this.scroller.y != snap.y ) { 1867 this.scroller.directionX = 0; 1868 this.scroller.directionY = 0; 1869 this.scroller.currentPage = snap; 1870 this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing); 1871 } 1872 } 1873 1874 if ( this.moved ) { 1875 this.scroller._execEvent('scrollEnd'); 1876 } 1877 }, 1878 1879 transitionTime: function (time) { 1880 time = time || 0; 1881 var durationProp = utils.style.transitionDuration; 1882 this.indicatorStyle[durationProp] = time + 'ms'; 1883 1884 if ( !time && utils.isBadAndroid ) { 1885 this.indicatorStyle[durationProp] = '0.0001ms'; 1886 // remove 0.0001ms 1887 var self = this; 1888 rAF(function() { 1889 if(self.indicatorStyle[durationProp] === '0.0001ms') { 1890 self.indicatorStyle[durationProp] = '0s'; 1891 } 1892 }); 1893 } 1894 }, 1895 1896 transitionTimingFunction: function (easing) { 1897 this.indicatorStyle[utils.style.transitionTimingFunction] = easing; 1898 }, 1899 1900 refresh: function () { 1901 this.transitionTime(); 1902 1903 if ( this.options.listenX && !this.options.listenY ) { 1904 this.indicatorStyle.display = this.scroller.hasHorizontalScroll ? 'block' : 'none'; 1905 } else if ( this.options.listenY && !this.options.listenX ) { 1906 this.indicatorStyle.display = this.scroller.hasVerticalScroll ? 'block' : 'none'; 1907 } else { 1908 this.indicatorStyle.display = this.scroller.hasHorizontalScroll || this.scroller.hasVerticalScroll ? 'block' : 'none'; 1909 } 1910 1911 if ( this.scroller.hasHorizontalScroll && this.scroller.hasVerticalScroll ) { 1912 utils.addClass(this.wrapper, 'iScrollBothScrollbars'); 1913 utils.removeClass(this.wrapper, 'iScrollLoneScrollbar'); 1914 1915 if ( this.options.defaultScrollbars && this.options.customStyle ) { 1916 if ( this.options.listenX ) { 1917 this.wrapper.style.right = '8px'; 1918 } else { 1919 this.wrapper.style.bottom = '8px'; 1920 } 1921 } 1922 } else { 1923 utils.removeClass(this.wrapper, 'iScrollBothScrollbars'); 1924 utils.addClass(this.wrapper, 'iScrollLoneScrollbar'); 1925 1926 if ( this.options.defaultScrollbars && this.options.customStyle ) { 1927 if ( this.options.listenX ) { 1928 this.wrapper.style.right = '2px'; 1929 } else { 1930 this.wrapper.style.bottom = '2px'; 1931 } 1932 } 1933 } 1934 1935 var r = this.wrapper.offsetHeight; // force refresh 1936 1937 if ( this.options.listenX ) { 1938 this.wrapperWidth = this.wrapper.clientWidth; 1939 if ( this.options.resize ) { 1940 this.indicatorWidth = Math.max(Math.round(this.wrapperWidth * this.wrapperWidth / (this.scroller.scrollerWidth || this.wrapperWidth || 1)), 8); 1941 this.indicatorStyle.width = this.indicatorWidth + 'px'; 1942 } else { 1943 this.indicatorWidth = this.indicator.clientWidth; 1944 } 1945
1946 this.maxPosX = this.wrapperWidth - this.indicatorWidth; 1947 1948 if ( this.options.shrink == 'clip' ) { 1949 this.minBoundaryX = -this.indicatorWidth + 8; 1950 this.maxBoundaryX = this.wrapperWidth - 8; 1951 } else { 1952 this.minBoundaryX = 0; 1953 this.maxBoundaryX = this.maxPosX; 1954 } 1955 1956 this.sizeRatioX = this.options.speedRatioX || (this.scroller.maxScrollX && (this.maxPosX / this.scroller.maxScrollX)); 1957 } 1958 1959 if ( this.options.listenY ) { 1960 this.wrapperHeight = this.wrapper.clientHeight; 1961 if ( this.options.resize ) { 1962 this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8); 1963 this.indicatorStyle.height = this.indicatorHeight + 'px'; 1964 } else { 1965 this.indicatorHeight = this.indicator.clientHeight; 1966 } 1967 1968 this.maxPosY = this.wrapperHeight - this.indicatorHeight; 1969 1970 if ( this.options.shrink == 'clip' ) { 1971 this.minBoundaryY = -this.indicatorHeight + 8; 1972 this.maxBoundaryY = this.wrapperHeight - 8; 1973 } else { 1974 this.minBoundaryY = 0; 1975 this.maxBoundaryY = this.maxPosY; 1976 } 1977 1978 this.maxPosY = this.wrapperHeight - this.indicatorHeight; 1979 this.sizeRatioY = this.options.speedRatioY || (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY)); 1980 } 1981 1982 this.updatePosition(); 1983 }, 1984 1985 updatePosition: function () { 1986 var x = this.options.listenX && Math.round(this.sizeRatioX * this.scroller.x) || 0, 1987 y = this.options.listenY && Math.round(this.sizeRatioY * this.scroller.y) || 0; 1988 1989 if ( !this.options.ignoreBoundaries ) { 1990 if ( x < this.minBoundaryX ) { 1991 if ( this.options.shrink == 'scale' ) { 1992 this.width = Math.max(this.indicatorWidth + x, 8); 1993 this.indicatorStyle.width = this.width + 'px'; 1994 } 1995 x = this.minBoundaryX; 1996 } else if ( x > this.maxBoundaryX ) { 1997 if ( this.options.shrink == 'scale' ) { 1998 this.width = Math.max(this.indicatorWidth - (x - this.maxPosX), 8); 1999 this.indicatorStyle.width = this.width + 'px'; 2000 x = this.maxPosX + this.indicatorWidth - this.width; 2001 } else { 2002 x = this.maxBoundaryX; 2003 } 2004 } else if ( this.options.shrink == 'scale' && this.width != this.indicatorWidth ) { 2005 this.width = this.indicatorWidth; 2006 this.indicatorStyle.width = this.width + 'px'; 2007 } 2008 2009 if ( y < this.minBoundaryY ) { 2010 if ( this.options.shrink == 'scale' ) { 2011 this.height = Math.max(this.indicatorHeight + y * 3, 8); 2012 this.indicatorStyle.height = this.height + 'px'; 2013 } 2014 y = this.minBoundaryY; 2015 } else if ( y > this.maxBoundaryY ) { 2016 if ( this.options.shrink == 'scale' ) { 2017 this.height = Math.max(this.indicatorHeight - (y - this.maxPosY) * 3, 8); 2018 this.indicatorStyle.height = this.height + 'px'; 2019 y = this.maxPosY + this.indicatorHeight - this.height; 2020 } else { 2021 y = this.maxBoundaryY; 2022 } 2023 } else if ( this.options.shrink == 'scale' && this.height != this.indicatorHeight ) { 2024 this.height = this.indicatorHeight; 2025 this.indicatorStyle.height = this.height + 'px'; 2026 } 2027 } 2028 2029 this.x = x; 2030 this.y = y; 2031 2032 if ( this.scroller.options.useTransform ) { 2033 this.indicatorStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.scroller.translateZ; 2034 } else { 2035 this.indicatorStyle.left = x + 'px'; 2036 this.indicatorStyle.top = y + 'px'; 2037 } 2038 }, 2039 2040 _pos: function (x, y) { 2041 if ( x < 0 ) { 2042 x = 0; 2043 } else if ( x > this.maxPosX ) { 2044 x = this.maxPosX; 2045 } 2046 2047 if ( y < 0 ) { 2048 y = 0; 2049 } else if ( y > this.maxPosY ) { 2050 y = this.maxPosY; 2051 } 2052 2053 x = this.options.listenX ? Math.round(x / this.sizeRatioX) : this.scroller.x; 2054 y = this.options.listenY ? Math.round(y / this.sizeRatioY) : this.scroller.y; 2055 2056 this.scroller.scrollTo(x, y); 2057 }, 2058 2059 fade: function (val, hold) { 2060 if ( hold && !this.visible ) { 2061 return; 2062 } 2063 2064 clearTimeout(this.fadeTimeout); 2065 this.fadeTimeout = null; 2066 2067 var time = val ? 250 : 500, 2068 delay = val ? 0 : 300; 2069 2070 val = val ? '1' : '0'; 2071 2072 this.wrapperStyle[utils.style.transitionDuration] = time + 'ms'; 2073 2074 this.fadeTimeout = setTimeout((function (val) { 2075 this.wrapperStyle.opacity = val; 2076 this.visible = +val; 2077 }).bind(this, val), delay); 2078 } 2079}; 2080 2081IScroll.utils = utils; 2082 2083if ( typeof module != 'undefined' && module.exports ) { 2084 module.exports = IScroll; 2085} else if ( typeof define == 'function' && define.amd ) { 2086 define( function () { return IScroll; } ); 2087} else { 2088 window.IScroll = IScroll; 2089} 2090 2091})(window, document, Math); 2092
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.