/src/iscroll.js
JavaScript | 1049 lines | 786 code | 190 blank | 73 comment | 254 complexity | 5362eba55798e45bdd7713ab8193755c MD5 | raw file
1/** 2 * 3 * * * * * * * * * * * * * * * * * * * * * * * * * * * * 4 * iScroll v4.0 Beta 4 5 * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6 * 7 * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/ 8 * Released under MIT license 9 * http://cubiq.org/dropbox/mit-license.txt 10 * 11 * Last updated: 2011.03.10 12 * 13 * * * * * * * * * * * * * * * * * * * * * * * * * * * * 14 * 15 */ 16 17(function(){ 18function iScroll (el, options) { 19 var that = this, doc = document, div, i; 20 21 that.wrapper = typeof el == 'object' ? el : doc.getElementById(el); 22 that.wrapper.style.overflow = 'hidden'; 23 that.scroller = that.wrapper.children[0]; 24 25 // Default options 26 that.options = { 27 HWTransition: true, // Experimental, internal use only 28 HWCompositing: true, // Experimental, internal use only 29 hScroll: true, 30 vScroll: true, 31 hScrollbar: true, 32 vScrollbar: true, 33 fixedScrollbar: isAndroid, 34 fadeScrollbar: (isIDevice && has3d) || !hasTouch, 35 hideScrollbar: isIDevice || !hasTouch, 36 scrollbarClass: '', 37 bounce: has3d, 38 bounceLock: false, 39 momentum: has3d, 40 lockDirection: true, 41 zoom: false, 42 zoomMin: 1, 43 zoomMax: 4, 44 snap: false, 45 pullToRefresh: false, 46 pullDownLabel: ['Pull down to refresh...', 'Release to refresh...', 'Loading...'], 47 pullUpLabel: ['Pull up to refresh...', 'Release to refresh...', 'Loading...'], 48 onPullDown: function () {}, 49 onPullUp: function () {}, 50 onScrollStart: null, 51 onScrollEnd: null, 52 onZoomStart: null, 53 onZoomEnd: null, 54 checkDOMChange: false // Experimental 55 }; 56 57 // User defined options 58 for (i in options) { 59 that.options[i] = options[i]; 60 } 61 62 that.options.HWCompositing = that.options.HWCompositing && hasCompositing; 63 that.options.HWTransition = that.options.HWTransition && hasCompositing; 64 65 if (that.options.HWCompositing) { 66 that.scroller.style.cssText += '-webkit-transition-property:-webkit-transform;-webkit-transform-origin:0 0;-webkit-transform:' + trnOpen + '0,0' + trnClose; 67 } else { 68 that.scroller.style.cssText += '-webkit-transition-property:top,left;-webkit-transform-origin:0 0;top:0;left:0'; 69 } 70 71 if (that.options.HWTransition) { 72 that.scroller.style.cssText += '-webkit-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-webkit-transition-duration:0;'; 73 } 74 75 that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar; 76 that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar; 77 78 that.pullDownToRefresh = that.options.pullToRefresh == 'down' || that.options.pullToRefresh == 'both'; 79 that.pullUpToRefresh = that.options.pullToRefresh == 'up' || that.options.pullToRefresh == 'both'; 80 81 if (that.pullDownToRefresh) { 82 div = doc.createElement('div'); 83 div.className = 'iScrollPullDown'; 84 div.innerHTML = '<span class="iScrollPullDownIcon"></span><span class="iScrollPullDownLabel">' + that.options.pullDownLabel[0] + '</span>\n'; 85 that.scroller.insertBefore(div, that.scroller.children[0]); 86 that.options.bounce = true; 87 that.pullDownEl = div; 88 that.pullDownLabel = div.getElementsByTagName('span')[1]; 89 } 90 91 if (that.pullUpToRefresh) { 92 div = doc.createElement('div'); 93 div.className = 'iScrollPullUp'; 94 div.innerHTML = '<span class="iScrollPullUpIcon"></span><span class="iScrollPullUpLabel">' + that.options.pullUpLabel[0] + '</span>\n'; 95 that.scroller.appendChild(div); 96 that.options.bounce = true; 97 that.pullUpEl = div; 98 that.pullUpLabel = div.getElementsByTagName('span')[1]; 99 } 100 101 that.refresh(); 102 103 that._bind(RESIZE_EV, window); 104 that._bind(START_EV); 105/* that._bind(MOVE_EV); 106 that._bind(END_EV); 107 that._bind(CANCEL_EV);*/ 108 109 if (hasGesture && that.options.zoom) { 110 that._bind('gesturestart'); 111 that.scroller.style.webkitTransform = that.scroller.style.webkitTransform + ' scale(1)'; 112 } 113 114 if (!hasTouch) { 115 that._bind('mousewheel'); 116 } 117 118 if (that.options.checkDOMChange) { 119 that.DOMChangeInterval = setInterval(function () { that._checkSize(); }, 250); 120 } 121} 122 123iScroll.prototype = { 124 x: 0, y: 0, 125 currPageX: 0, currPageY: 0, 126 pagesX: [], pagesY: [], 127 offsetBottom: 0, 128 offsetTop: 0, 129 scale: 1, lastScale: 1, 130 contentReady: true, 131 132 handleEvent: function (e) { 133 var that = this; 134 135 switch(e.type) { 136 case START_EV: that._start(e); break; 137 case MOVE_EV: that._move(e); break; 138 case END_EV: 139 case CANCEL_EV: that._end(e); break; 140 case 'webkitTransitionEnd': that._transitionEnd(e); break; 141 case RESIZE_EV: that._resize(); break; 142 case 'gesturestart': that._gestStart(e); break; 143 case 'gesturechange': that._gestChange(e); break; 144 case 'gestureend': 145 case 'gesturecancel': that._gestEnd(e); break; 146 case 'mousewheel': that._wheel(e); break; 147 } 148 }, 149 150 _scrollbar: function (dir) { 151 var that = this, 152 doc = document, 153 bar; 154 155 if (!that[dir + 'Scrollbar']) { 156 if (that[dir + 'ScrollbarWrapper']) { 157 that[dir + 'ScrollbarIndicator'].style.webkitTransform = ''; // Should free some mem 158 that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']); 159 that[dir + 'ScrollbarWrapper'] = null; 160 that[dir + 'ScrollbarIndicator'] = null; 161 } 162 163 return; 164 } 165 166 if (!that[dir + 'ScrollbarWrapper']) { 167 // Create the scrollbar wrapper 168 bar = doc.createElement('div'); 169 if (that.options.scrollbarClass) { 170 bar.className = that.options.scrollbarClass + dir.toUpperCase(); 171 } else { 172 bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:7px' : 'width:7px;bottom:7px;top:2px;right:1px'); 173 } 174 bar.style.cssText += 'pointer-events:none;-webkit-transition-property:opacity;-webkit-transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1'); 175 176 that.wrapper.appendChild(bar); 177 that[dir + 'ScrollbarWrapper'] = bar; 178 179 // Create the scrollbar indicator 180 bar = doc.createElement('div'); 181 if (!that.options.scrollbarClass) { 182 bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);-webkit-background-clip:padding-box;-webkit-box-sizing:border-box;' + (dir == 'h' ? 'height:100%;-webkit-border-radius:4px 3px;' : 'width:100%;-webkit-border-radius:3px 4px;'); 183 } 184 bar.style.cssText += 'pointer-events:none;-webkit-transition-property:-webkit-transform;-webkit-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-webkit-transition-duration:0;-webkit-transform:' + trnOpen + '0,0' + trnClose; 185 186 that[dir + 'ScrollbarWrapper'].appendChild(bar); 187 that[dir + 'ScrollbarIndicator'] = bar; 188 } 189 190 if (dir == 'h') { 191 that.hScrollbarSize = that.hScrollbarWrapper.clientWidth; 192 that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8); 193 that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px'; 194 that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize; 195 that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX; 196 } else { 197 that.vScrollbarSize = that.vScrollbarWrapper.clientHeight; 198 that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8); 199 that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px'; 200 that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize; 201 that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY; 202 } 203 204 // Reset position 205 that._indicatorPos(dir, true); 206 }, 207 208 _resize: function () { 209 var that = this; 210 211 //if (that.options.momentum) that._unbind('webkitTransitionEnd'); 212 213 setTimeout(function () { 214 that.refresh(); 215 }, 0); 216 }, 217 218 _checkSize: function () { 219 var that = this, 220 scrollerW, 221 scrollerH; 222 223 if (that.moved || that.zoomed || !that.contentReady) return; 224 225 scrollerW = m.round(that.scroller.offsetWidth * that.scale), 226 scrollerH = m.round((that.scroller.offsetHeight - that.offsetBottom - that.offsetTop) * that.scale); 227 228 if (scrollerW == that.scrollerW && scrollerH == that.scrollerH) return; 229 230 that.refresh(); 231 }, 232 233 _pos: function (x, y) { 234 var that = this; 235 236 that.x = that.hScroll ? x : 0; 237 that.y = that.vScroll ? y : 0; 238 239 that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')'; 240// that.scroller.style.left = that.x + 'px'; 241// that.scroller.style.top = that.y + 'px'; 242 243 that._indicatorPos('h'); 244 that._indicatorPos('v'); 245 }, 246 247 _indicatorPos: function (dir, hidden) { 248 var that = this, 249 pos = dir == 'h' ? that.x : that.y; 250 251 if (!that[dir + 'Scrollbar']) return; 252 253 pos = that[dir + 'ScrollbarProp'] * pos; 254 255 if (pos < 0) { 256 pos = that.options.fixedScrollbar ? 0 : pos + pos*3; 257 if (that[dir + 'ScrollbarIndicatorSize'] + pos < 9) pos = -that[dir + 'ScrollbarIndicatorSize'] + 8; 258 } else if (pos > that[dir + 'ScrollbarMaxScroll']) { 259 pos = that.options.fixedScrollbar ? that[dir + 'ScrollbarMaxScroll'] : pos + (pos - that[dir + 'ScrollbarMaxScroll'])*3; 260 if (that[dir + 'ScrollbarIndicatorSize'] + that[dir + 'ScrollbarMaxScroll'] - pos < 9) pos = that[dir + 'ScrollbarIndicatorSize'] + that[dir + 'ScrollbarMaxScroll'] - 8; 261 } 262 that[dir + 'ScrollbarWrapper'].style.webkitTransitionDelay = '0'; 263 that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1'; 264 that[dir + 'ScrollbarIndicator'].style.webkitTransform = trnOpen + (dir == 'h' ? pos + 'px,0' : '0,' + pos + 'px') + trnClose; 265 }, 266 267 _transitionTime: function (time) { 268 var that = this; 269 270 time += 'ms'; 271 that.scroller.style.webkitTransitionDuration = time; 272 273 if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionDuration = time; 274 if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionDuration = time; 275 }, 276 277 _start: function (e) { 278 var that = this, 279 point = hasTouch ? e.changedTouches[0] : e, 280 matrix; 281 282 that.moved = false; 283 284 e.preventDefault(); 285 286 if (hasTouch && e.touches.length == 2 && that.options.zoom && hasGesture && !that.zoomed) { 287 that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft*2) / 2 - that.x; 288 that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop*2) / 2 - that.y; 289 } 290 291 that.moved = false; 292 that.distX = 0; 293 that.distY = 0; 294 that.absDistX = 0; 295 that.absDistY = 0; 296 that.dirX = 0; 297 that.dirY = 0; 298 that.returnTime = 0; 299 300 that._transitionTime(0); 301 302 if (that.options.momentum) { 303 if (that.scrollInterval) { 304 clearInterval(that.scrollInterval); 305 that.scrollInterval = null; 306 } 307 308 if (that.options.HWCompositing) { 309 matrix = new WebKitCSSMatrix(window.getComputedStyle(that.scroller, null).webkitTransform); 310 if (matrix.m41 != that.x || matrix.m42 != that.y) { 311 that._unbind('webkitTransitionEnd'); 312 that._pos(matrix.m41, matrix.m42); 313 } 314 } else { 315 matrix = window.getComputedStyle(that.scroller, null); 316 if (that.x + 'px' != matrix.left || that.y + 'px' != matrix.top) { 317 that._unbind('webkitTransitionEnd'); 318 that._pos(matrix.left.replace(/[^0-9]/g)*1, matrix.top.replace(/[^0-9]/g)*1); 319 } 320 } 321 322 } 323 324 that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)'; 325 if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)'; 326 if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)'; 327 that.startX = that.x; 328 that.startY = that.y; 329 that.pointX = point.pageX; 330 that.pointY = point.pageY; 331 332 that.startTime = e.timeStamp; 333 334 if (that.options.onScrollStart) that.options.onScrollStart.call(that); 335 336 // Registering/unregistering of events is done to preserve resources on Android 337// setTimeout(function () { 338// that._unbind(START_EV); 339 that._bind(MOVE_EV); 340 that._bind(END_EV); 341 that._bind(CANCEL_EV); 342// }, 0); 343 }, 344 345 _move: function (e) { 346 if (hasTouch && e.touches.length > 1) return; 347 348 var that = this, 349 point = hasTouch ? e.changedTouches[0] : e, 350 deltaX = point.pageX - that.pointX, 351 deltaY = point.pageY - that.pointY, 352 newX = that.x + deltaX, 353 newY = that.y + deltaY; 354 355 e.preventDefault(); 356 357 that.pointX = point.pageX; 358 that.pointY = point.pageY; 359 360 // Slow down if outside of the boundaries 361 if (newX > 0 || newX < that.maxScrollX) { 362 newX = that.options.bounce ? that.x + (deltaX / 2.4) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX; 363 } 364 if (newY > 0 || newY < that.maxScrollY) { 365 newY = that.options.bounce ? that.y + (deltaY / 2.4) : newY >= 0 || that.maxScrollY >= 0 ? 0 : that.maxScrollY; 366 367 // Pull down to refresh 368 if (that.options.pullToRefresh && that.contentReady) { 369 if (that.pullDownToRefresh && newY > that.offsetBottom) { 370 that.pullDownEl.className = 'iScrollPullDown flip'; 371 that.pullDownLabel.innerText = that.options.pullDownLabel[1]; 372 } else if (that.pullDownToRefresh && that.pullDownEl.className.match('flip')) { 373 that.pullDownEl.className = 'iScrollPullDown'; 374 that.pullDownLabel.innerText = that.options.pullDownLabel[0]; 375 } 376 377 if (that.pullUpToRefresh && newY < that.maxScrollY - that.offsetTop) { 378 that.pullUpEl.className = 'iScrollPullUp flip'; 379 that.pullUpLabel.innerText = that.options.pullUpLabel[1]; 380 } else if (that.pullUpToRefresh && that.pullUpEl.className.match('flip')) { 381 that.pullUpEl.className = 'iScrollPullUp'; 382 that.pullUpLabel.innerText = that.options.pullUpLabel[0]; 383 } 384 } 385 } 386 387 if (that.absDistX < 4 && that.absDistY < 4) { 388 that.distX += deltaX; 389 that.distY += deltaY; 390 that.absDistX = m.abs(that.distX); 391 that.absDistY = m.abs(that.distY); 392 return; 393 } 394 395 // Lock direction 396 if (that.options.lockDirection) { 397 if (that.absDistX > that.absDistY+3) { 398 newY = that.y; 399 deltaY = 0; 400 } else if (that.absDistY > that.absDistX+3) { 401 newX = that.x; 402 deltaX = 0; 403 } 404 } 405 406 that.moved = true; 407 that._pos(newX, newY); 408 that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; 409 that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; 410 411 if (e.timeStamp - that.startTime > 300) { 412 that.startTime = e.timeStamp; 413 that.startX = that.x; 414 that.startY = that.y; 415 } 416 }, 417 418 _end: function (e) { 419 if (hasTouch && e.touches.length != 0) return; 420 421 var that = this, 422 point = hasTouch ? e.changedTouches[0] : e, 423 target, ev, 424 momentumX = { dist:0, time:0 }, 425 momentumY = { dist:0, time:0 }, 426 duration = e.timeStamp - that.startTime, 427 newPosX = that.x, newPosY = that.y, 428 newDuration, 429 snap; 430 431// that._bind(START_EV); 432 that._unbind(MOVE_EV); 433 that._unbind(END_EV); 434 that._unbind(CANCEL_EV); 435 436 if (that.zoomed) return; 437 438 if (!that.moved) { 439 if (hasTouch) { 440 if (that.doubleTapTimer && that.options.zoom) { 441 // Double tapped 442 clearTimeout(that.doubleTapTimer); 443 that.doubleTapTimer = null; 444 that.zoom(that.pointX, that.pointY, that.scale == 1 ? 2 : 1); 445 } else { 446 that.doubleTapTimer = setTimeout(function () { 447 that.doubleTapTimer = null; 448 449 // Find the last touched element 450 target = point.target; 451 while (target.nodeType != 1) { 452 target = target.parentNode; 453 } 454 455 ev = document.createEvent('MouseEvents'); 456 ev.initMouseEvent('click', true, true, e.view, 1, 457 point.screenX, point.screenY, point.clientX, point.clientY, 458 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 459 0, null); 460 ev._fake = true; 461 target.dispatchEvent(ev); 462 }, that.options.zoom ? 250 : 0); 463 } 464 } 465 466 that._resetPos(); 467 return; 468 } 469 470 if (that.pullDownToRefresh && that.contentReady && that.pullDownEl.className.match('flip')) { 471 that.pullDownEl.className = 'iScrollPullDown loading'; 472 that.pullDownLabel.innerText = that.options.pullDownLabel[2]; 473 that.scroller.style.marginTop = '0'; 474 that.offsetBottom = 0; 475 that.refresh(); 476 that.contentReady = false; 477 that.options.onPullDown(); 478 } 479 480 if (that.pullUpToRefresh && that.contentReady && that.pullUpEl.className.match('flip')) { 481 that.pullUpEl.className = 'iScrollPullUp loading'; 482 that.pullUpLabel.innerText = that.options.pullUpLabel[2]; 483 that.scroller.style.marginBottom = '0'; 484 that.offsetTop = 0; 485 that.refresh(); 486 that.contentReady = false; 487 that.options.onPullUp(); 488 } 489 490 if (duration < 300 && that.options.momentum) { 491 momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX; 492 momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y : 0), that.options.bounce ? that.wrapperH : 0) : momentumY; 493 494 newPosX = that.x + momentumX.dist; 495 newPosY = that.y + momentumY.dist; 496 497 if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 }; 498 if ((that.y > 0 && newPosY > 0) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 }; 499 } 500 501 if (momentumX.dist || momentumY.dist) { 502 newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); 503 504 // Do we need to snap? 505 if (that.options.snap) { 506 snap = that._snap(newPosX, newPosY); 507 newPosX = snap.x; 508 newPosY = snap.y; 509 newDuration = m.max(snap.time, newDuration); 510 } 511 512/* if (newPosX > 0 || newPosX < that.maxScrollX || newPosY > 0 || newPosY < that.maxScrollY) { 513 // Subtle change of scroller motion 514 that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)'; 515 if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)'; 516 if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)'; 517 }*/ 518 519 that.scrollTo(newPosX, newPosY, newDuration); 520 return; 521 } 522 523 // Do we need to snap? 524 if (that.options.snap) { 525 snap = that._snap(that.x, that.y); 526 if (snap.x != that.x || snap.y != that.y) { 527 that.scrollTo(snap.x, snap.y, snap.time); 528 } 529 return; 530 } 531 532 that._resetPos(); 533 }, 534 535 _resetPos: function (time) { 536 var that = this, 537 resetX = that.x, 538 resetY = that.y; 539 540 if (that.x >= 0) resetX = 0; 541 else if (that.x < that.maxScrollX) resetX = that.maxScrollX; 542 543 if (that.y >= 0 || that.maxScrollY > 0) resetY = 0; 544 else if (that.y < that.maxScrollY) resetY = that.maxScrollY; 545 546 if (resetX == that.x && resetY == that.y) { 547 if (that.moved) { 548 if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end 549 that.moved = false; 550 } 551 552 if (that.zoomed) { 553 if (that.options.onZoomEnd) that.options.onZoomEnd.call(that); // Execute custom code on scroll end 554 that.zoomed = false; 555 } 556 557 if (that.hScrollbar && that.options.hideScrollbar) { 558 that.hScrollbarWrapper.style.webkitTransitionDelay = '300ms'; 559 that.hScrollbarWrapper.style.opacity = '0'; 560 } 561 if (that.vScrollbar && that.options.hideScrollbar) { 562 that.vScrollbarWrapper.style.webkitTransitionDelay = '300ms'; 563 that.vScrollbarWrapper.style.opacity = '0'; 564 } 565 566 return; 567 } 568 569 if (time === undefined) time = 200; 570 571 // Invert ease 572 if (time) { 573 that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)'; 574 if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)'; 575 if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)'; 576 } 577 578 that.scrollTo(resetX, resetY, time); 579 }, 580 581 _timedScroll: function (destX, destY, runtime) { 582 var that = this, 583 startX = that.x, startY = that.y, 584 startTime = (new Date).getTime(), 585 easeOut; 586 587 that._transitionTime(0); 588 589 if (that.scrollInterval) { 590 clearInterval(that.scrollInterval); 591 that.scrollInterval = null; 592 } 593 594 that.scrollInterval = setInterval(function () { 595 var now = (new Date).getTime(), 596 newX, newY; 597 598 if (now >= startTime + runtime) { 599 clearInterval(that.scrollInterval); 600 that.scrollInterval = null; 601 602 that._pos(destX, destY); 603 that._transitionEnd(); 604 return; 605 } 606 607 now = (now - startTime) / runtime - 1; 608 easeOut = m.sqrt(1 - now * now); 609 newX = (destX - startX) * easeOut + startX; 610 newY = (destY - startY) * easeOut + startY; 611 that._pos(newX, newY); 612 }, 20); 613 }, 614 615 _transitionEnd: function (e) { 616 var that = this; 617 618 if (e) e.stopPropagation(); 619 620 that._unbind('webkitTransitionEnd'); 621 622 that._resetPos(that.returnTime); 623 that.returnTime = 0; 624 }, 625 626 627 /** 628 * 629 * Gestures 630 * 631 */ 632 _gestStart: function (e) { 633 var that = this; 634 635 that._transitionTime(0); 636 that.lastScale = 1; 637 638 if (that.options.onZoomStart) that.options.onZoomStart.call(that); 639 640 that._unbind('gesturestart'); 641 that._bind('gesturechange'); 642 that._bind('gestureend'); 643 that._bind('gesturecancel'); 644 }, 645 646 _gestChange: function (e) { 647 var that = this, 648 scale = that.scale * e.scale, 649 x, y, relScale; 650 651 that.zoomed = true; 652 653 if (scale < that.options.zoomMin) scale = that.options.zoomMin; 654 else if (scale > that.options.zoomMax) scale = that.options.zoomMax; 655 656 relScale = scale / that.scale; 657 x = that.originX - that.originX * relScale + that.x; 658 y = that.originY - that.originY * relScale + that.y; 659 that.scroller.style.webkitTransform = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + scale + ')'; 660 that.lastScale = relScale; 661 }, 662 663 _gestEnd: function (e) { 664 var that = this, 665 scale = that.scale, 666 lastScale = that.lastScale; 667 668 that.scale = scale * lastScale; 669 if (that.scale < that.options.zoomMin + 0.05) that.scale = that.options.zoomMin; 670 else if (that.scale > that.options.zoomMax - 0.05) that.scale = that.options.zoomMax; 671 lastScale = that.scale / scale; 672 that.x = that.originX - that.originX * lastScale + that.x; 673 that.y = that.originY - that.originY * lastScale + that.y; 674 675 that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')'; 676 677 setTimeout(function () { 678 that.refresh(); 679 }, 0); 680 681 that._bind('gesturestart'); 682 that._unbind('gesturechange'); 683 that._unbind('gestureend'); 684 that._unbind('gesturecancel'); 685 }, 686 687 _wheel: function (e) { 688 var that = this, 689 deltaX = that.x + e.wheelDeltaX / 12, 690 deltaY = that.y + e.wheelDeltaY / 12; 691 692 if (deltaX > 0) deltaX = 0; 693 else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX; 694 695 if (deltaY > 0) deltaY = 0; 696 else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY; 697 698 that.scrollTo(deltaX, deltaY, 0); 699 }, 700 701 702 /** 703 * 704 * Utilities 705 * 706 */ 707 _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { 708 var that = this, 709 deceleration = 0.0006, 710 speed = m.abs(dist) / time, 711 newDist = (speed * speed) / (2 * deceleration), 712 newTime = 0, outsideDist = 0; 713 714 // Proportinally reduce speed if we are outside of the boundaries 715 if (dist > 0 && newDist > maxDistUpper) { 716 outsideDist = size / (6 / (newDist / speed * deceleration)); 717 maxDistUpper = maxDistUpper + outsideDist; 718 that.returnTime = 800 / size * outsideDist + 100; 719 speed = speed * maxDistUpper / newDist; 720 newDist = maxDistUpper; 721 } else if (dist < 0 && newDist > maxDistLower) { 722 outsideDist = size / (6 / (newDist / speed * deceleration)); 723 maxDistLower = maxDistLower + outsideDist; 724 that.returnTime = 800 / size * outsideDist + 100; 725 speed = speed * maxDistLower / newDist; 726 newDist = maxDistLower; 727 } 728 729 newDist = newDist * (dist < 0 ? -1 : 1); 730 newTime = speed / deceleration; 731 732 return { dist: newDist, time: m.round(newTime) }; 733 }, 734 735 _offset: function (el, tree) { 736 var left = -el.offsetLeft, 737 top = -el.offsetTop; 738 739 if (!tree) return { x: left, y: top }; 740 741 while (el = el.offsetParent) { 742 left -= el.offsetLeft; 743 top -= el.offsetTop; 744 } 745 746 return { x: left, y: top }; 747 }, 748 749 _snap: function (x, y) { 750 var that = this, 751 i, l, 752 page, time, 753 sizeX, sizeY; 754 755 // Check page X 756 page = that.pagesX.length-1; 757 for (i=0, l=that.pagesX.length; i<l; i++) { 758 if (x >= that.pagesX[i]) { 759 page = i; 760 break; 761 } 762 } 763 if (page == that.currPageX && page > 0 && that.dirX < 0) page--; 764 x = that.pagesX[page]; 765 sizeX = m.abs(x - that.pagesX[that.currPageX]); 766 sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0; 767 that.currPageX = page; 768 769 // Check page Y 770 page = that.pagesY.length-1; 771 for (i=0; i<page; i++) { 772 if (y >= that.pagesY[i]) { 773 page = i; 774 break; 775 } 776 } 777 if (page == that.currPageY && page > 0 && that.dirY < 0) page--; 778 y = that.pagesY[page]; 779 sizeY = m.abs(y - that.pagesY[that.currPageY]); 780 sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0; 781 that.currPageY = page; 782 783 // Snap with constant speed (proportional duration) 784 time = m.round(m.max(sizeX, sizeY)) || 200; 785 786 return { x: x, y: y, time: time }; 787 }, 788 789 _bind: function (type, el) { 790 (el || this.scroller).addEventListener(type, this, false); 791 }, 792 793 _unbind: function (type, el) { 794 (el || this.scroller).removeEventListener(type, this, false); 795 }, 796 797 798 /** 799 * 800 * Public methods 801 * 802 */ 803 destroy: function () { 804 var that = this; 805 806 if (that.options.checkDOMChange) clearTimeout(that.DOMChangeInterval); 807 808 // Remove pull to refresh 809 if (that.pullDownToRefresh) { 810 that.pullDownEl.parentNode.removeChild(that.pullDownEl); 811 } 812 if (that.pullUpToRefresh) { 813 that.pullUpEl.parentNode.removeChild(that.pullUpEl); 814 } 815 816 // Remove the scrollbars 817 that.hScrollbar = false; 818 that.vScrollbar = false; 819 that._scrollbar('h'); 820 that._scrollbar('v'); 821 822 // Free some mem 823 that.scroller.style.webkitTransform = ''; 824 825 // Remove the event listeners 826 that._unbind('webkitTransitionEnd'); 827 that._unbind(RESIZE_EV); 828 that._unbind(START_EV); 829 that._unbind(MOVE_EV); 830 that._unbind(END_EV); 831 that._unbind(CANCEL_EV); 832 833 if (that.options.zoom) { 834 that._unbind('gesturestart'); 835 that._unbind('gesturechange'); 836 that._unbind('gestureend'); 837 that._unbind('gesturecancel'); 838 } 839 }, 840 841 refresh: function () { 842 var that = this, 843 pos = 0, page = 0, 844 i, l, els, 845 oldHeight, offsets, 846 loading; 847 848 if (that.pullDownToRefresh) { 849 loading = that.pullDownEl.className.match('loading'); 850 if (loading && !that.contentReady) { 851 oldHeight = that.scrollerH; 852 that.contentReady = true; 853 that.pullDownEl.className = 'iScrollPullDown'; 854 that.pullDownLabel.innerText = that.options.pullDownLabel[0]; 855 that.offsetBottom = that.pullDownEl.offsetHeight; 856 that.scroller.style.marginTop = -that.offsetBottom + 'px'; 857 } else if (!loading) { 858 that.offsetBottom = that.pullDownEl.offsetHeight; 859 that.scroller.style.marginTop = -that.offsetBottom + 'px'; 860 } 861 } 862 863 if (that.pullUpToRefresh) { 864 loading = that.pullUpEl.className.match('loading'); 865 if (loading && !that.contentReady) { 866 oldHeight = that.scrollerH; 867 that.contentReady = true; 868 that.pullUpEl.className = 'iScrollPullUp'; 869 that.pullUpLabel.innerText = that.options.pullUpLabel[0]; 870 that.offsetTop = that.pullUpEl.offsetHeight; 871 that.scroller.style.marginBottom = -that.offsetTop + 'px'; 872 } else if (!loading) { 873 that.offsetTop = that.pullUpEl.offsetHeight; 874 that.scroller.style.marginBottom = -that.offsetTop + 'px'; 875 } 876 } 877 878 that.wrapperW = that.wrapper.clientWidth; 879 that.wrapperH = that.wrapper.clientHeight; 880 that.scrollerW = m.round(that.scroller.offsetWidth * that.scale); 881 that.scrollerH = m.round((that.scroller.offsetHeight - that.offsetBottom - that.offsetTop) * that.scale); 882 that.maxScrollX = that.wrapperW - that.scrollerW; 883 that.maxScrollY = that.wrapperH - that.scrollerH; 884 that.dirX = 0; 885 that.dirY = 0; 886 887 that._transitionTime(0); 888 889 that.hScroll = that.options.hScroll && that.maxScrollX < 0; 890 that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH); 891 that.hScrollbar = that.hScroll && that.options.hScrollbar; 892 that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH; 893 894 // Prepare the scrollbars 895 that._scrollbar('h'); 896 that._scrollbar('v'); 897 898 // Snap 899 if (typeof that.options.snap == 'string') { 900 that.pagesX = []; 901 that.pagesY = []; 902 els = that.scroller.querySelectorAll(that.options.snap); 903 for (i=0, l=els.length; i<l; i++) { 904 pos = that._offset(els[i]); 905 that.pagesX[i] = pos.x < that.maxScrollX ? that.maxScrollX : pos.x * that.scale; 906 that.pagesY[i] = pos.y < that.maxScrollY ? that.maxScrollY : pos.y * that.scale; 907 } 908 } else if (that.options.snap) { 909 that.pagesX = []; 910 while (pos >= that.maxScrollX) { 911 that.pagesX[page] = pos; 912 pos = pos - that.wrapperW; 913 page++; 914 } 915 if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1]; 916 917 pos = 0; 918 page = 0; 919 that.pagesY = []; 920 while (pos >= that.maxScrollY) { 921 that.pagesY[page] = pos; 922 pos = pos - that.wrapperH; 923 page++; 924 } 925 if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1]; 926 } 927 928 // Recalculate wrapper offsets 929 if (that.options.zoom) { 930 offsets = that._offset(that.wrapper, true); 931 that.wrapperOffsetLeft = -offsets.x; 932 that.wrapperOffsetTop = -offsets.y; 933 } 934 935 if (oldHeight && that.y == 0) { 936 oldHeight = oldHeight - that.scrollerH + that.y; 937 that.scrollTo(0, oldHeight, 0); 938 } 939 940 that._resetPos(); 941 }, 942 943 scrollTo: function (x, y, time, relative) { 944 var that = this; 945 946 if (relative) { 947 x = that.x - x; 948 y = that.y - y; 949 } 950 951 time = !time || (m.round(that.x) == m.round(x) && m.round(that.y) == m.round(y)) ? 0 : time; 952 953 that.moved = true; 954 955 if (!that.options.HWTransition) { 956 that._timedScroll(x, y, time); 957 return; 958 } 959 960 if (time) that._bind('webkitTransitionEnd'); 961 that._transitionTime(time); 962 that._pos(x, y); 963 if (!time) setTimeout(function () { that._transitionEnd(); }, 0); 964 }, 965 966 scrollToElement: function (el, time) { 967 var that = this, pos; 968 el = el.nodeType ? el : that.scroller.querySelector(el); 969 if (!el) return; 970 971 pos = that._offset(el); 972 pos.x = pos.x > 0 ? 0 : pos.x < that.maxScrollX ? that.maxScrollX : pos.x; 973 pos.y = pos.y > 0 ? 0 : pos.y < that.maxScrollY ? that.maxScrollY : pos.y; 974 time = time === undefined ? m.max(m.abs(pos.x)*2, m.abs(pos.y)*2) : time; 975 976 that.scrollTo(pos.x, pos.y, time); 977 }, 978 979 scrollToPage: function (pageX, pageY, time) { 980 var that = this, x, y; 981 982 if (that.options.snap) { 983 pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX; 984 pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY; 985 986 pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX; 987 pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY; 988 989 that.currPageX = pageX; 990 that.currPageY = pageY; 991 x = that.pagesX[pageX]; 992 y = that.pagesY[pageY]; 993 } else { 994 x = -that.wrapperW * pageX; 995 y = -that.wrapperH * pageY; 996 if (x < that.maxScrollX) x = that.maxScrollX; 997 if (y < that.maxScrollY) y = that.maxScrollY; 998 } 999 1000 that.scrollTo(x, y, time || 400); 1001 }, 1002 1003 zoom: function (x, y, scale) { 1004 var that = this, 1005 relScale = scale / that.scale; 1006 1007 x = x - that.wrapperOffsetLeft - that.x; 1008 y = y - that.wrapperOffsetTop - that.y; 1009 that.x = x - x * relScale + that.x; 1010 that.y = y - y * relScale + that.y; 1011 1012 that.scale = scale; 1013 1014 if (that.options.onZoomStart) that.options.onZoomStart.call(that); 1015 1016 that.refresh(); 1017 1018 that._bind('webkitTransitionEnd'); 1019 that._transitionTime(200); 1020 1021 setTimeout(function () { 1022 that.zoomed = true; 1023 that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + scale + ')'; 1024 }, 0); 1025 } 1026}; 1027 1028 1029var has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(), 1030 hasTouch = 'ontouchstart' in window, 1031 hasGesture = 'ongesturestart' in window, 1032// hasHashChange = 'onhashchange' in window, 1033// hasTransitionEnd = 'onwebkittransitionend' in window, 1034 hasCompositing = 'WebKitTransitionEvent' in window, 1035 isIDevice = (/iphone|ipad/gi).test(navigator.appVersion), 1036 isAndroid = (/android/gi).test(navigator.appVersion), 1037 RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize', 1038 START_EV = hasTouch ? 'touchstart' : 'mousedown', 1039 MOVE_EV = hasTouch ? 'touchmove' : 'mousemove', 1040 END_EV = hasTouch ? 'touchend' : 'mouseup', 1041 CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup', 1042 trnOpen = 'translate' + (has3d ? '3d(' : '('), 1043 trnClose = has3d ? ',0)' : ')', 1044 m = Math; 1045 1046if (typeof exports !== 'undefined') exports.iScroll = iScroll; 1047else window.iScroll = iScroll; 1048 1049})();