* Released under MIT license
* http://cubiq.org/dropbox/mit-license.txt
*
- * Version 3.1beta1 - Last updated: 2010.05.06
+ * Version 3.2.1 - Last updated: 2010.06.03
*
*/
+(function(){
+
function iScroll (el, options) {
this.element = typeof el == 'object' ? el : document.getElementById(el);
this.wrapper = this.element.parentNode;
- this.wrapper.style.overflow = 'hidden';
- this.wrapper.style.position = 'relative';
this.element.style.webkitTransitionProperty = '-webkit-transform';
this.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
this.element.style.webkitTransitionDuration = '0';
// Get options
this.options = {
bounce: true,
+ checkDOMChanges: true,
+ topOnDOMChanges: false,
hScrollBar: true,
vScrollBar: true
};
this.element.addEventListener('touchstart', this);
this.element.addEventListener('touchmove', this);
this.element.addEventListener('touchend', this);
- this.element.addEventListener('DOMSubtreeModified', this);
window.addEventListener('orientationchange', this);
+
+ if (this.options.checkDOMChanges) {
+ this.element.addEventListener('DOMSubtreeModified', this);
+ }
}
iScroll.prototype = {
- _x: 0,
- _y: 0,
+ x: 0,
+ y: 0,
handleEvent: function (e) {
switch (e.type) {
case 'touchmove': this.onTouchMove(e); break;
case 'touchend': this.onTouchEnd(e); break;
case 'webkitTransitionEnd': this.onTransitionEnd(e); break;
- case 'orientationchange': this.refresh(); /*this.scrollTo(0,0,'0');*/ break;
- case 'DOMSubtreeModified': this.refresh(); break;
+ case 'orientationchange': this.refresh(); break;
+ case 'DOMSubtreeModified': this.onDOMModified(e); break;
+ }
+ },
+
+ onDOMModified: function (e) {
+ this.refresh();
+
+ if (this.options.topOnDOMChanges && (this.x!=0 || this.y!=0)) {
+ this.scrollTo(0,0,'0');
}
},
refresh: function () {
- this.element.style.webkitTransitionDuration = '0';
this.scrollWidth = this.wrapper.clientWidth;
this.scrollHeight = this.wrapper.clientHeight;
this.maxScrollX = this.scrollWidth - this.element.offsetWidth;
this.maxScrollY = this.scrollHeight - this.element.offsetHeight;
-
+
var resetX = this.x, resetY = this.y;
- if (this.scrollX && this.x < this.maxScrollX) {
- resetX = this.maxScrollX;
+ if (this.scrollX) {
+ if (this.maxScrollX >= 0) {
+ resetX = 0;
+ } else if (this.x < this.maxScrollX) {
+ resetX = this.maxScrollX;
+ }
}
- if (this.scrollY && this.y < this.maxScrollY) {
- resetY = this.maxScrollY;
+ if (this.scrollY) {
+ if (this.maxScrollY >= 0) {
+ resetY = 0;
+ } else if (this.y < this.maxScrollY) {
+ resetY = this.maxScrollY;
+ }
+ }
+ if (resetX!=this.x || resetY!=this.y) {
+ this.scrollTo(resetX,resetY,'0');
}
- // If content width/lenght has been modified, reset scroller position
- this.scrollTo(resetX,resetY,'0');
this.scrollX = this.element.offsetWidth > this.scrollWidth ? true : false;
this.scrollY = this.element.offsetHeight > this.scrollHeight ? true : false;
// Update horizontal scrollbar
if (this.options.hScrollBar && this.scrollX) {
- this.scrollBarX = new scrollbar('horizontal', this.wrapper);
+ this.scrollBarX = (this.scrollBarX instanceof scrollbar) ? this.scrollBarX : new scrollbar('horizontal', this.wrapper);
this.scrollBarX.init(this.scrollWidth, this.element.offsetWidth);
} else if (this.scrollBarX) {
this.scrollBarX = this.scrollBarX.remove();
// Update vertical scrollbar
if (this.options.vScrollBar && this.scrollY) {
- this.scrollBarY = new scrollbar('vertical', this.wrapper);
+ this.scrollBarY = (this.scrollBarY instanceof scrollbar) ? this.scrollBarY : new scrollbar('vertical', this.wrapper);
this.scrollBarY.init(this.scrollHeight, this.element.offsetHeight);
} else if (this.scrollBarY) {
this.scrollBarY = this.scrollBarY.remove();
}
},
- get x() {
- return this._x;
- },
-
- get y() {
- return this._y;
- },
-
setPosition: function (x, y) {
- this._x = x !== null ? x : this._x;
- this._y = y !== null ? y : this._y;
+ this.x = x !== null ? x : this.x;
+ this.y = y !== null ? y : this.y;
- this.element.style.webkitTransform = 'translate3d(' + this._x + 'px,' + this._y + 'px,0)';
+ this.element.style.webkitTransform = 'translate3d(' + this.x + 'px,' + this.y + 'px,0)';
// Move the scrollbars
if (this.scrollBarX) {
- this.scrollBarX.setPosition(this.scrollBarX.maxScroll / this.maxScrollX * this._x);
+ this.scrollBarX.setPosition(this.scrollBarX.maxScroll / this.maxScrollX * this.x);
}
if (this.scrollBarY) {
- this.scrollBarY.setPosition(this.scrollBarY.maxScroll / this.maxScrollY * this._y);
+ this.scrollBarY.setPosition(this.scrollBarY.maxScroll / this.maxScrollY * this.y);
}
},
this.scrollStartX = this.x;
this.touchStartY = e.touches[0].pageY;
- this.scrollStartY = this.y;
+ this.scrollStartY = this.y;
this.scrollStartTime = e.timeStamp;
this.moved = false;
if (e.targetTouches.length != 1) {
return false;
}
+
e.preventDefault();
- var leftDelta = this.scrollX === true ? e.touches[0].pageX - this.touchStartX : 0;
- var topDelta = this.scrollY === true ? e.touches[0].pageY - this.touchStartY : 0;
- if (this.x > 0 || this.x < this.maxScrollX) {
- leftDelta = Math.round(leftDelta / 4); // Slow down if outside of the boundaries
- }
+ var leftDelta = this.scrollX === true ? e.touches[0].pageX - this.touchStartX : 0,
+ topDelta = this.scrollY === true ? e.touches[0].pageY - this.touchStartY : 0,
+ newX = this.x + leftDelta,
+ newY = this.y + topDelta;
- if (this.y > 0 || this.y < this.maxScrollY) {
- topDelta = Math.round(topDelta / 4); // Slow down if outside of the boundaries
+ // Slow down if outside of the boundaries
+ if (newX > 0 || newX < this.maxScrollX) {
+ newX = this.options.bounce ? Math.round(this.x + leftDelta / 4) : this.x;
+ }
+ if (newY > 0 || newY < this.maxScrollY) {
+ newY = this.options.bounce ? Math.round(this.y + topDelta / 4) : this.y;
}
if (this.scrollBarX && !this.scrollBarX.visible) {
this.scrollBarY.show();
}
- this.setPosition(this.x + leftDelta, this.y + topDelta);
+ this.setPosition(newX, newY);
this.touchStartX = e.touches[0].pageX;
this.touchStartY = e.touches[0].pageY;
}
if (!this.moved) {
+ // Find the last touched element
+ var theTarget = e.changedTouches[0].target;
+ if (theTarget.nodeType == 3) {
+ theTarget = theTarget.parentNode;
+ }
+ // Create the fake event
var theEvent = document.createEvent('MouseEvents');
- theEvent.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
- e.changedTouches[0].target.dispatchEvent(theEvent);
+ theEvent.initMouseEvent("click", true, true, document.defaultView,
+ e.detail, e.screenX, e.screenY, e.clientX, e.clientY,
+ e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
+ e.button, e.relatedTarget);
+ theTarget.dispatchEvent(theEvent);
return false;
}
var momentumX = this.scrollX === true
? this.momentum(this.x - this.scrollStartX,
time,
- -this.x + 50,
- this.x + this.element.offsetWidth - this.scrollWidth + 50)
+ this.options.bounce ? -this.x + this.scrollWidth/4 : -this.x,
+ this.options.bounce ? this.x + this.element.offsetWidth - this.scrollWidth + this.scrollWidth/4 : this.x + this.element.offsetWidth - this.scrollWidth)
: { dist: 0, time: 0 };
var momentumY = this.scrollY === true
? this.momentum(this.y - this.scrollStartY,
time,
- -this.y + /*this.scrollHeight/3*/ 50,
- this.y + this.element.offsetHeight - this.scrollHeight + /*this.scrollHeight/3*/ 50)
+ this.options.bounce ? -this.y + this.scrollHeight/4 : -this.y,
+ this.options.bounce ? this.y + this.element.offsetHeight - this.scrollHeight + this.scrollHeight/4 : this.y + this.element.offsetHeight - this.scrollHeight)
: { dist: 0, time: 0 };
if (!momentumX.dist && !momentumY.dist) {
- this.onTransitionEnd(); // I know, I know... This is lame
+ this.resetPosition();
return false;
}
- var newDuration = Math.max(momentumX.time, momentumY.time);
+ var newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1); // The minimum animation length must be 1ms
var newPositionX = this.x + momentumX.dist;
var newPositionY = this.y + momentumY.dist;
onTransitionEnd: function () {
this.element.removeEventListener('webkitTransitionEnd', this);
this.resetPosition();
-
- // Hide the scrollbars
- if (this.scrollBarX) {
- this.scrollBarX.hide();
- }
- if (this.scrollBarY) {
- this.scrollBarY.hide();
- }
},
resetPosition: function () {
- var resetX = resetY = null;
- if (this.x > 0 || this.x < this.maxScrollX) {
- resetX = this.x >= 0 ? 0 : this.maxScrollX;
+ var resetX = this.x,
+ resetY = this.y;
+
+ if (this.x >= 0) {
+ resetX = 0;
+ } else if (this.x < this.maxScrollX) {
+ resetX = this.maxScrollX;
}
- if (this.y > 0 || this.y < this.maxScrollY) {
- resetY = this.y >= 0 ? 0 : this.maxScrollY;
+ if (this.y >= 0) {
+ resetY = 0;
+ } else if (this.y < this.maxScrollY) {
+ resetY = this.maxScrollY;
}
- if (resetX !== null || resetY !== null) {
+ if (resetX != this.x || resetY != this.y) {
this.scrollTo(resetX, resetY, '500ms');
- if (this.scrollBarX) {
- this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * (resetX || this.x), '500ms');
+ if (this.scrollBarX && resetX != this.x) {
+ this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * resetX, '500ms');
}
- if (this.scrollBarY) {
- this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * (resetY || this.y), '500ms');
+ if (this.scrollBarY && resetY != this.y) {
+ this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * resetY, '500ms');
}
}
+
+ // Hide the scrollbars
+ if (this.scrollBarX) {
+ this.scrollBarX.hide();
+ }
+ if (this.scrollBarY) {
+ this.scrollBarY.hide();
+ }
},
scrollTo: function (destX, destY, runtime) {
this.setPosition(destX, destY);
},
- momentum: function (dist, time, maxDist1, maxDist2) {
- friction = 0.1;
- deceleration = 1.5;
-
- var speed = Math.abs(dist) / time * 1000;
- var newDist = speed * speed / (20 * friction) / 1000;
+ momentum: function (dist, time, maxDistUpper, maxDistLower) {
+ var friction = 0.1,
+ deceleration = 1.5,
+ speed = Math.abs(dist) / time * 1000,
+ newDist = speed * speed / (20 * friction) / 1000;
// Proportinally reduce speed if we are outside of the boundaries
- if (dist > 0 && maxDist1 !== undefined && newDist > maxDist1) {
- speed = speed * maxDist1 / newDist;
- newDist = maxDist1;
+ if (dist > 0 && newDist > maxDistUpper) {
+ speed = speed * maxDistUpper / newDist;
+ newDist = maxDistUpper;
}
- if (dist < 0 && maxDist2 !== undefined && newDist > maxDist2) {
- speed = speed * maxDist2 / newDist;
- newDist = maxDist2;
+ if (dist < 0 && newDist > maxDistLower) {
+ speed = speed * maxDistLower / newDist;
+ newDist = maxDistLower;
}
newDist = newDist * (dist < 0 ? -1 : 1);
- var newTime = -speed / -deceleration;
- if (newTime < 1) { // We can't go back in time
- newTime = 1;
- }
+ var newTime = speed / deceleration;
return { dist: Math.round(newDist), time: Math.round(newTime) };
}
this.bar.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
this.bar.style.webkitTransform = 'translate3d(0,0,0)';
this.bar.style.webkitTransitionProperty = '-webkit-transform,opacity';
- this.bar.style.webkitTransitionDuration = '0,250ms';
+ this.bar.style.webkitTransitionDuration = '0,300ms';
this.bar.style.pointerEvents = 'none';
this.bar.style.opacity = '0';
},
scrollTo: function (pos, runtime) {
- this.bar.style.webkitTransitionDuration = (runtime || '400ms') + ',250ms';
+ this.bar.style.webkitTransitionDuration = (runtime || '400ms') + ',300ms';
this.setPosition(pos);
},
return null;
}
};
+
+// Expose iScroll to the world
+window.iScroll = iScroll;
+})();