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