Horizontal and some scroll tweaks

This commit is contained in:
Max Lynch
2013-10-25 10:50:17 -05:00
parent b78b76ca9a
commit e0cf20abfd
4 changed files with 204 additions and 88 deletions

134
dist/js/ionic.js vendored
View File

@ -2488,9 +2488,9 @@ window.ionic = {
var _this = this; var _this = this;
// Extend the options with our defaults // Extend the options with our defaults
ionic.Utils.extend(opts, { opts = ionic.Utils.extend({
decelerationRate: ionic.views.ScrollView.prototype.DECEL_RATE_NORMAL, decelerationRate: ionic.views.ScrollView.prototype.DECEL_RATE_NORMAL,
dragThresholdY: 10, dragThreshold: 10,
resistance: 2, resistance: 2,
scrollEventName: 'momentumScrolled', scrollEventName: 'momentumScrolled',
intertialEventInterval: 50, intertialEventInterval: 50,
@ -2499,7 +2499,7 @@ window.ionic = {
isVerticalEnabled: true, isVerticalEnabled: true,
isHorizontalEnabled: false, isHorizontalEnabled: false,
bounceTime: 600 //how long to take when bouncing back in a rubber band bounceTime: 600 //how long to take when bouncing back in a rubber band
}); }, opts);
ionic.Utils.extend(this, opts); ionic.Utils.extend(this, opts);
@ -2599,6 +2599,7 @@ window.ionic = {
destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
duration = speed / deceleration; duration = speed / deceleration;
/*
// Check if the final destination needs to be rubber banded // Check if the final destination needs to be rubber banded
if ( destination < lowerMargin ) { if ( destination < lowerMargin ) {
// We have dragged too far down, snap back to the maximum // We have dragged too far down, snap back to the maximum
@ -2611,6 +2612,7 @@ window.ionic = {
distance = Math.abs(current) + destination; distance = Math.abs(current) + destination;
duration = distance / speed; duration = distance / speed;
} }
*/
console.log('Momentum of time', time, speed, destination, duration); console.log('Momentum of time', time, speed, destination, duration);
return { return {
@ -2636,12 +2638,20 @@ window.ionic = {
var el = this.el; var el = this.el;
this.x = x; if(x !== null) {
this.y = y; this.x = x;
} else {
x = this.x;
}
if(y !== null) {
this.y = y;
} else {
y = this.y;
}
el.style.webkitTransitionTimingFunction = easing; el.style.webkitTransitionTimingFunction = easing;
el.style.webkitTransitionDuration = time; el.style.webkitTransitionDuration = time;
el.style.webkitTransform = 'translate3d(0,' + y + 'px, 0)'; el.style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px, 0)';
// Start triggering events as the element scrolls from inertia. // Start triggering events as the element scrolls from inertia.
// This is important because we need to receive scroll events // This is important because we need to receive scroll events
@ -2662,12 +2672,6 @@ window.ionic = {
}, },
_initDrag: function() {
this._endTransition();
this._isStopped = false;
},
_endTransition: function() { _endTransition: function() {
this._isDragging = false; this._isDragging = false;
@ -2681,6 +2685,11 @@ window.ionic = {
}, },
_initDrag: function() {
this._endTransition();
this._isStopped = false;
},
/** /**
* Initialize a drag by grabbing the content area to drag, and any other * Initialize a drag by grabbing the content area to drag, and any other
@ -2691,12 +2700,17 @@ window.ionic = {
this._initDrag(); this._initDrag();
var scrollLeft = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0;
this.x = scrollLeft;
this.y = scrollTop;
this._drag = { this._drag = {
direction: 'v', direction: 'v',
y: scrollTop, pointX: e.gesture.touches[0].pageX,
pointY: e.gesture.touches[0].pageY, pointY: e.gesture.touches[0].pageY,
startX: scrollLeft,
startY: scrollTop, startY: scrollTop,
resist: 1, resist: 1,
startTime: Date.now() startTime: Date.now()
@ -2707,6 +2721,9 @@ window.ionic = {
/** /**
* Process the drag event to move the item to the left or right. * Process the drag event to move the item to the left or right.
*
* This function needs to be as fast as possible to make sure scrolling
* performance is high.
*/ */
_handleDrag: function(e) { _handleDrag: function(e) {
var _this = this; var _this = this;
@ -2727,37 +2744,69 @@ window.ionic = {
// Stop any default events during the drag // Stop any default events during the drag
e.preventDefault(); e.preventDefault();
var px = e.gesture.touches[0].pageX;
var py = e.gesture.touches[0].pageY; var py = e.gesture.touches[0].pageY;
var deltaY = py - _this._drag.pointY;
console.log("Delta y", deltaY);
var deltaX = px - _this._drag.pointX;
var deltaY = py - _this._drag.pointY;
//console.log("Delta x", deltaX);
//console.log("Delta y", deltaY);
_this._drag.pointX = px;
_this._drag.pointY = py; _this._drag.pointY = py;
// Check if we should start dragging. Check if we've dragged past the threshold. // Check if we should start dragging. Check if we've dragged past the threshold.
if(!_this._isDragging && (Math.abs(e.gesture.deltaY) > _this.dragThresholdY)) { if(!_this._isDragging &&
((Math.abs(e.gesture.deltaY) > _this.dragThreshold) ||
(Math.abs(e.gesture.deltaX) > _this.dragThreshold))) {
_this._isDragging = true; _this._isDragging = true;
} }
if(_this._isDragging) { if(_this._isDragging) {
var drag = _this._drag; var drag = _this._drag;
// Request an animation frame to batch DOM reads/writes
window.requestAnimationFrame(function() { window.requestAnimationFrame(function() {
// We are dragging, grab the current content height // We are dragging, grab the current content height
// and the height of the parent container
var totalWidth = _this.el.scrollWidth;
var totalHeight = _this.el.offsetHeight; var totalHeight = _this.el.offsetHeight;
var parentWidth = _this.el.parentNode.offsetWidth;
var parentHeight = _this.el.parentNode.offsetHeight; var parentHeight = _this.el.parentNode.offsetHeight;
// Grab current timestamp to keep our speend, etc.
// calculations in a window
var timestamp = Date.now(); var timestamp = Date.now();
// Calculate the new Y point for the container // Calculate the new Y point for the container
// TODO: Only enable certain axes
var newX = _this.x + deltaX;
var newY = _this.y + deltaY; var newY = _this.y + deltaY;
if(newX > 0 || (-newX + parentWidth) > totalWidth) {
// Rubber band
newX = _this.x + deltaX / 3;
}
// Check if the dragging is beyond the bottom or top // Check if the dragging is beyond the bottom or top
if(newY > 0 || (-newY + parentHeight) > totalHeight) { if(newY > 0 || (-newY + parentHeight) > totalHeight) {
// Rubber band // Rubber band
newY = _this.y + deltaY / 3;//(-_this.resistance); newY = _this.y + deltaY / 3;//(-_this.resistance);
} }
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)';
if(!_this.isHorizontalEnabled) {
newX = 0;
}
if(!_this.isVerticalEnabled) {
newY = 0;
}
console.log(newX, newY);
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
// Store the last points
_this.x = newX;
_this.y = newY; _this.y = newY;
// Check if we need to reset the drag initial states if we've // Check if we need to reset the drag initial states if we've
@ -2765,11 +2814,14 @@ window.ionic = {
if(timestamp - drag.startTime > 300) { if(timestamp - drag.startTime > 300) {
console.log('Resetting timer'); console.log('Resetting timer');
drag.startTime = timestamp; drag.startTime = timestamp;
drag.startX = _this.x;
drag.startY = _this.y; drag.startY = _this.y;
} }
// Trigger a scroll event
ionic.trigger(_this.scrollEventName, { ionic.trigger(_this.scrollEventName, {
target: _this.el, target: _this.el,
scrollLeft: -newX,
scrollTop: -newY scrollTop: -newY
}); });
}); });
@ -2799,41 +2851,42 @@ window.ionic = {
// animate to that position // animate to that position
_animateToStop: function(e) { _animateToStop: function(e) {
var _this = this; var _this = this;
if(this._drag.direction == 'v') {
this._animateToStopVertical(e);
} else {
this._animateToStopHorizontal(e);
}
},
_animateToStopHorizontal: function(e) {
},
_animateToStopVertical: function(e) {
var _this = this;
window.requestAnimationFrame(function() { window.requestAnimationFrame(function() {
var drag = _this._drag; var drag = _this._drag;
// Calculate the viewport height and the height of the content // Calculate the viewport height and the height of the content
var totalHeight = _this.el.offsetHeight; var totalWidth = _this.el.scrollWidth;
var parentHeight = _this.el.parentNode.offsetHeight; var totalHeight = _this.el.scrollHeight;
var parentWidth = _this.el.offsetWidth;
var parentHeight = _this.el.offsetHeight;
// Calculate how long we've been dragging for, with a max of 300ms // Calculate how long we've been dragging for, with a max of 300ms
var duration = Math.min(300, (Date.now()) - _this._drag.startTime); var duration = Math.min(300, (Date.now()) - _this._drag.startTime);
//var newX = Math.round(this.x), var newX = Math.round(_this.x);
var newY = Math.round(_this.y); var newY = Math.round(_this.y);
//distanceX = Math.abs(newX - this.startX), //distanceX = Math.abs(newX - this.startX),
//var distanceY = Math.abs(newY - drag.startY); //var distanceY = Math.abs(newY - drag.startY);
var momentum = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth);
var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight);
//var newX = momentumX.destination; //var newX = momentumX.destination;
newY = momentum.destination; newX = momentumX.destination;
var time = momentum.duration; newY = momentumY.destination;
var timeX = momentumX.duration;
var timeY = momentumY.duration;
// Check if we need to rubber band back
if(_this.x > 0) {
_this.scrollTo(0, 0, _this.bounceTime);
} else if((-_this.x + parentWidth) > totalWidth) {
_this.scrollTo(0, totalWidth - parentWidth, _this.bounceTime);
}
if(_this.y > 0) { if(_this.y > 0) {
_this.scrollTo(0, 0, _this.bounceTime); _this.scrollTo(0, 0, _this.bounceTime);
@ -2846,7 +2899,8 @@ window.ionic = {
var el = _this.el; var el = _this.el;
// Turn on animation // Turn on animation
_this.scrollTo(0, newY, time); _this.scrollTo(newX, null, timeX);
_this.scrollTo(null, newY, timeX);
}); });
} }
}; };

View File

@ -5,9 +5,9 @@
var _this = this; var _this = this;
// Extend the options with our defaults // Extend the options with our defaults
ionic.Utils.extend(opts, { opts = ionic.Utils.extend({
decelerationRate: ionic.views.ScrollView.prototype.DECEL_RATE_NORMAL, decelerationRate: ionic.views.ScrollView.prototype.DECEL_RATE_NORMAL,
dragThresholdY: 10, dragThreshold: 10,
resistance: 2, resistance: 2,
scrollEventName: 'momentumScrolled', scrollEventName: 'momentumScrolled',
intertialEventInterval: 50, intertialEventInterval: 50,
@ -16,7 +16,7 @@
isVerticalEnabled: true, isVerticalEnabled: true,
isHorizontalEnabled: false, isHorizontalEnabled: false,
bounceTime: 600 //how long to take when bouncing back in a rubber band bounceTime: 600 //how long to take when bouncing back in a rubber band
}); }, opts);
ionic.Utils.extend(this, opts); ionic.Utils.extend(this, opts);
@ -116,6 +116,7 @@
destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
duration = speed / deceleration; duration = speed / deceleration;
/*
// Check if the final destination needs to be rubber banded // Check if the final destination needs to be rubber banded
if ( destination < lowerMargin ) { if ( destination < lowerMargin ) {
// We have dragged too far down, snap back to the maximum // We have dragged too far down, snap back to the maximum
@ -128,6 +129,7 @@
distance = Math.abs(current) + destination; distance = Math.abs(current) + destination;
duration = distance / speed; duration = distance / speed;
} }
*/
console.log('Momentum of time', time, speed, destination, duration); console.log('Momentum of time', time, speed, destination, duration);
return { return {
@ -153,12 +155,20 @@
var el = this.el; var el = this.el;
this.x = x; if(x !== null) {
this.y = y; this.x = x;
} else {
x = this.x;
}
if(y !== null) {
this.y = y;
} else {
y = this.y;
}
el.style.webkitTransitionTimingFunction = easing; el.style.webkitTransitionTimingFunction = easing;
el.style.webkitTransitionDuration = time; el.style.webkitTransitionDuration = time;
el.style.webkitTransform = 'translate3d(0,' + y + 'px, 0)'; el.style.webkitTransform = 'translate3d(' + x + 'px,' + y + 'px, 0)';
// Start triggering events as the element scrolls from inertia. // Start triggering events as the element scrolls from inertia.
// This is important because we need to receive scroll events // This is important because we need to receive scroll events
@ -179,12 +189,6 @@
}, },
_initDrag: function() {
this._endTransition();
this._isStopped = false;
},
_endTransition: function() { _endTransition: function() {
this._isDragging = false; this._isDragging = false;
@ -198,6 +202,11 @@
}, },
_initDrag: function() {
this._endTransition();
this._isStopped = false;
},
/** /**
* Initialize a drag by grabbing the content area to drag, and any other * Initialize a drag by grabbing the content area to drag, and any other
@ -208,12 +217,17 @@
this._initDrag(); this._initDrag();
var scrollLeft = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0;
this.x = scrollLeft;
this.y = scrollTop;
this._drag = { this._drag = {
direction: 'v', direction: 'v',
y: scrollTop, pointX: e.gesture.touches[0].pageX,
pointY: e.gesture.touches[0].pageY, pointY: e.gesture.touches[0].pageY,
startX: scrollLeft,
startY: scrollTop, startY: scrollTop,
resist: 1, resist: 1,
startTime: Date.now() startTime: Date.now()
@ -224,6 +238,9 @@
/** /**
* Process the drag event to move the item to the left or right. * Process the drag event to move the item to the left or right.
*
* This function needs to be as fast as possible to make sure scrolling
* performance is high.
*/ */
_handleDrag: function(e) { _handleDrag: function(e) {
var _this = this; var _this = this;
@ -244,37 +261,69 @@
// Stop any default events during the drag // Stop any default events during the drag
e.preventDefault(); e.preventDefault();
var px = e.gesture.touches[0].pageX;
var py = e.gesture.touches[0].pageY; var py = e.gesture.touches[0].pageY;
var deltaY = py - _this._drag.pointY;
console.log("Delta y", deltaY);
var deltaX = px - _this._drag.pointX;
var deltaY = py - _this._drag.pointY;
//console.log("Delta x", deltaX);
//console.log("Delta y", deltaY);
_this._drag.pointX = px;
_this._drag.pointY = py; _this._drag.pointY = py;
// Check if we should start dragging. Check if we've dragged past the threshold. // Check if we should start dragging. Check if we've dragged past the threshold.
if(!_this._isDragging && (Math.abs(e.gesture.deltaY) > _this.dragThresholdY)) { if(!_this._isDragging &&
((Math.abs(e.gesture.deltaY) > _this.dragThreshold) ||
(Math.abs(e.gesture.deltaX) > _this.dragThreshold))) {
_this._isDragging = true; _this._isDragging = true;
} }
if(_this._isDragging) { if(_this._isDragging) {
var drag = _this._drag; var drag = _this._drag;
// Request an animation frame to batch DOM reads/writes
window.requestAnimationFrame(function() { window.requestAnimationFrame(function() {
// We are dragging, grab the current content height // We are dragging, grab the current content height
// and the height of the parent container
var totalWidth = _this.el.scrollWidth;
var totalHeight = _this.el.offsetHeight; var totalHeight = _this.el.offsetHeight;
var parentWidth = _this.el.parentNode.offsetWidth;
var parentHeight = _this.el.parentNode.offsetHeight; var parentHeight = _this.el.parentNode.offsetHeight;
// Grab current timestamp to keep our speend, etc.
// calculations in a window
var timestamp = Date.now(); var timestamp = Date.now();
// Calculate the new Y point for the container // Calculate the new Y point for the container
// TODO: Only enable certain axes
var newX = _this.x + deltaX;
var newY = _this.y + deltaY; var newY = _this.y + deltaY;
if(newX > 0 || (-newX + parentWidth) > totalWidth) {
// Rubber band
newX = _this.x + deltaX / 3;
}
// Check if the dragging is beyond the bottom or top // Check if the dragging is beyond the bottom or top
if(newY > 0 || (-newY + parentHeight) > totalHeight) { if(newY > 0 || (-newY + parentHeight) > totalHeight) {
// Rubber band // Rubber band
newY = _this.y + deltaY / 3;//(-_this.resistance); newY = _this.y + deltaY / 3;//(-_this.resistance);
} }
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)';
if(!_this.isHorizontalEnabled) {
newX = 0;
}
if(!_this.isVerticalEnabled) {
newY = 0;
}
console.log(newX, newY);
// Update the new translated Y point of the container
_this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
// Store the last points
_this.x = newX;
_this.y = newY; _this.y = newY;
// Check if we need to reset the drag initial states if we've // Check if we need to reset the drag initial states if we've
@ -282,11 +331,14 @@
if(timestamp - drag.startTime > 300) { if(timestamp - drag.startTime > 300) {
console.log('Resetting timer'); console.log('Resetting timer');
drag.startTime = timestamp; drag.startTime = timestamp;
drag.startX = _this.x;
drag.startY = _this.y; drag.startY = _this.y;
} }
// Trigger a scroll event
ionic.trigger(_this.scrollEventName, { ionic.trigger(_this.scrollEventName, {
target: _this.el, target: _this.el,
scrollLeft: -newX,
scrollTop: -newY scrollTop: -newY
}); });
}); });
@ -316,41 +368,42 @@
// animate to that position // animate to that position
_animateToStop: function(e) { _animateToStop: function(e) {
var _this = this; var _this = this;
if(this._drag.direction == 'v') {
this._animateToStopVertical(e);
} else {
this._animateToStopHorizontal(e);
}
},
_animateToStopHorizontal: function(e) {
},
_animateToStopVertical: function(e) {
var _this = this;
window.requestAnimationFrame(function() { window.requestAnimationFrame(function() {
var drag = _this._drag; var drag = _this._drag;
// Calculate the viewport height and the height of the content // Calculate the viewport height and the height of the content
var totalHeight = _this.el.offsetHeight; var totalWidth = _this.el.scrollWidth;
var parentHeight = _this.el.parentNode.offsetHeight; var totalHeight = _this.el.scrollHeight;
var parentWidth = _this.el.offsetWidth;
var parentHeight = _this.el.offsetHeight;
// Calculate how long we've been dragging for, with a max of 300ms // Calculate how long we've been dragging for, with a max of 300ms
var duration = Math.min(300, (Date.now()) - _this._drag.startTime); var duration = Math.min(300, (Date.now()) - _this._drag.startTime);
//var newX = Math.round(this.x), var newX = Math.round(_this.x);
var newY = Math.round(_this.y); var newY = Math.round(_this.y);
//distanceX = Math.abs(newX - this.startX), //distanceX = Math.abs(newX - this.startX),
//var distanceY = Math.abs(newY - drag.startY); //var distanceY = Math.abs(newY - drag.startY);
var momentum = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth);
var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight);
//var newX = momentumX.destination; //var newX = momentumX.destination;
newY = momentum.destination; newX = momentumX.destination;
var time = momentum.duration; newY = momentumY.destination;
var timeX = momentumX.duration;
var timeY = momentumY.duration;
// Check if we need to rubber band back
if(_this.x > 0) {
_this.scrollTo(0, 0, _this.bounceTime);
} else if((-_this.x + parentWidth) > totalWidth) {
_this.scrollTo(0, totalWidth - parentWidth, _this.bounceTime);
}
if(_this.y > 0) { if(_this.y > 0) {
_this.scrollTo(0, 0, _this.bounceTime); _this.scrollTo(0, 0, _this.bounceTime);
@ -363,7 +416,8 @@
var el = _this.el; var el = _this.el;
// Turn on animation // Turn on animation
_this.scrollTo(0, newY, time); _this.scrollTo(newX, null, timeX);
_this.scrollTo(null, newY, timeX);
}); });
} }
}; };

View File

@ -38,7 +38,7 @@
for(var i = 0; i < 100; i++) { for(var i = 0; i < 100; i++) {
var li = document.createElement('li'); var li = document.createElement('li');
li.className = 'list-item'; li.className = 'item';
li.innerHTML = 'Item ' + i; li.innerHTML = 'Item ' + i;
s.firstElementChild.appendChild(li); s.firstElementChild.appendChild(li);
} }

View File

@ -24,22 +24,30 @@
<a href="#" class="button button-danger button-clear">Delete</a> <a href="#" class="button button-danger button-clear">Delete</a>
</div> </div>
<div id="scroller" class="scroll" style="margin-top:44px"> <div id="scroller" class="scroll" style="margin-top:44px">
<div id="filler" style="width: 1400px; height: 300px; background: url('tree_bark.png') repeat;"></div> <div id="filler" style="width: 4400px; height: 300px; background: url('tree_bark.png') repeat;"></div>
</div> </div>
</section> </section>
<script src="../dist/js/ionic.js"></script> <script src="../dist/js/ionic.js"></script>
<script> <script>
var s = document.getElementById('scroller'); var s = document.getElementById('scroller');
for(var i = 0; i < 100; i++) { var w = s.scrollWidth;
var li = document.createElement('li');
li.className = 'list-item'; for(var i = 0; i < w; i++) {
li.innerHTML = 'Item ' + i; if((i % 500) === 0) {
s.firstElementChild.appendChild(li); var l = document.createElement('span');
l.innerText = i + 'px'
l.style.position = 'absolute';
l.style.left = i + 'px';
l.style.top = '20px';
s.appendChild(l);
}
} }
var scroll = new ionic.views.ScrollView({ var scroll = new ionic.views.ScrollView({
el: s el: s,
isHorizontalEnabled: true,
isVerticalEnabled: false
}); });
</script> </script>
</body> </body>