Files
ionic-framework/js/views/slideBox.js
2013-10-16 10:26:10 -05:00

330 lines
10 KiB
JavaScript

/**
* The SlideBox is a swipeable, slidable, slideshowable box. Think of any image gallery
* or iOS "dot" pager gallery, or maybe a carousel.
*
* Each screen fills the full width and height of the viewport, and screens can
* be swiped between, or set to automatically transition.
*/
(function(ionic) {
'use strict';
ionic.views.SlideBox = function(opts) {
var _this = this;
this.el = opts.el;
this.pager = this.el.querySelector('.slide-box-pager');
// The drag threshold is the pixel delta that will trigger a drag (to
// avoid accidental dragging)
this.dragThresholdX = opts.dragThresholdX || 10;
// The velocity threshold is a velocity of drag that indicates a "swipe". This
// number is taken from hammer.js's calculations
this.velocityXThreshold = opts.velocityXThreshold || 0.3;
// Initialize the slide index to the first page and update the pager
this.slideIndex = 0;
this._updatePager();
// Listen for drag and release events
window.ionic.onGesture('drag', function(e) {
_this._handleDrag(e);
}, this.el);
window.ionic.onGesture('release', function(e) {
_this._handleEndDrag(e);
}, this.el);
};
ionic.views.SlideBox.prototype = {
/**
* Tell the pager to update itself if content is added or
* removed.
*/
update: function() {
this._updatePager();
},
prependSlide: function(el) {
var content = this.el.firstElementChild;
if(!content) { return; }
var slideWidth = content.offsetWidth;
var offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
var newOffsetX = Math.min(0, offsetX - slideWidth);
content.insertBefore(el, content.firstChild);
content.classList.remove('slide-box-animating');
content.style.webkitTransform = 'translate3d(' + newOffsetX + 'px, 0, 0)';
this._prependPagerIcon();
this.slideIndex = (this.slideIndex + 1) % content.children.length;
this._updatePager();
},
appendSlide: function(el) {
var content = this.el.firstElementChild;
if(!content) { return; }
content.classList.remove('slide-box-animating');
content.appendChild(el);
this._appendPagerIcon();
this._updatePager();
},
removeSlide: function(index) {
var content = this.el.firstElementChild;
if(!content) { return; }
var items = this.el.firstElementChild;
items.removeChild(items.firstElementChild);
var slideWidth = content.offsetWidth;
var offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
var newOffsetX = Math.min(0, offsetX + slideWidth);
content.classList.remove('slide-box-animating');
content.style.webkitTransform = 'translate3d(' + newOffsetX + 'px, 0, 0)';
this._removePagerIcon();
this.slideIndex = Math.max(0, (this.slideIndex - 1) % content.children.length);
this._updatePager();
},
/**
* Slide to the given slide index.
*
* @param {int} the index of the slide to animate to.
*/
slideToSlide: function(index) {
var content = this.el.firstElementChild;
if(!content) {
return;
}
// Get the width of one slide
var slideWidth = content.offsetWidth;
// Calculate the new offsetX position which is just
// N slides to the left, where N is the given index
var offsetX = index * slideWidth;
// Calculate the max X position we'd allow based on how many slides
// there are.
var maxX = Math.max(0, content.children.length - 1) * slideWidth;
// Bounds the offset X position in the range maxX >= offsetX >= 0
offsetX = offsetX < 0 ? 0 : offsetX > maxX ? maxX : offsetX;
// Animate and slide the slides over
content.classList.add('slide-box-animating');
content.style.webkitTransform = 'translate3d(' + -offsetX + 'px, 0, 0)';
// Update the slide index
this.slideIndex = Math.ceil(offsetX / slideWidth);
this._updatePager();
},
/**
* Get the currently set slide index. This method
* is updated before any transitions run, so the
* value could be early.
*
* @return {int} the current slide index
*/
getSlideIndex: function() {
return this.slideIndex;
},
_appendPagerIcon: function() {
if(!this.pager || !this.pager.children.length) { return; }
var newPagerChild = this.pager.children[0].cloneNode();
this.pager.appendChild(newPagerChild);
},
_prependPagerIcon: function() {
if(!this.pager || !this.pager.children.length) { return; }
var newPagerChild = this.pager.children[0].cloneNode();
this.pager.insertBefore(newPagerChild, this.pager.firstChild);
},
_removePagerIcon: function() {
if(!this.pager || !this.pager.children.length) { return; }
this.pager.removeChild(this.pager.firstElementChild);
},
/**
* If we have a pager, update the active page when the current slide
* changes.
*/
_updatePager: function() {
if(!this.pager) {
return;
}
var numPagerChildren = this.pager.children.length;
if(!numPagerChildren) {
// No children to update
return;
}
// Update the active state of the pager icons
for(var i = 0, j = this.pager.children.length; i < j; i++) {
if(i == this.slideIndex) {
this.pager.children[i].classList.add('active');
} else {
this.pager.children[i].classList.remove('active');
}
}
},
_initDrag: function() {
this._isDragging = false;
this._drag = null;
},
_handleEndDrag: function(e) {
var _this = this,
finalOffsetX, content, ratio, slideWidth, totalWidth, offsetX;
window.requestAnimationFrame(function() {
// We didn't have a drag, so just init and leave
if(!_this._drag) {
_this._initDrag();
return;
}
// We did have a drag, so we need to snap to the correct spot
// Grab the content layer
content = _this._drag.content;
// Enable transition duration
content.classList.add('slide-box-animating');
// Grab the current offset X position
offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
// Calculate how wide a single slide is, and their total width
slideWidth = content.offsetWidth;
totalWidth = content.offsetWidth * content.children.length;
// Calculate how far in this slide we've dragged
ratio = (offsetX % slideWidth) / slideWidth;
if(ratio >= 0) {
// Anything greater than zero is too far left, this is an extreme case
// TODO: Do we need this anymore?
finalOffsetX = 0;
} else if(ratio >= -0.5) {
// We are less than half-way through a drag
// Sliiide to the left
finalOffsetX = Math.max(0, Math.floor(Math.abs(offsetX) / slideWidth) * slideWidth);
} else {
// We are more than half-way through a drag
// Sliiide to the right
finalOffsetX = Math.min(totalWidth - slideWidth, Math.ceil(Math.abs(offsetX) / slideWidth) * slideWidth);
}
if(e.gesture.velocityX > _this.velocityXThreshold) {
if(e.gesture.direction == 'left') {
_this.slideToSlide(_this.slideIndex + 1);
} else if(e.gesture.direction == 'right') {
_this.slideToSlide(_this.slideIndex - 1);
}
} else {
// Calculate the new slide index (or "page")
_this.slideIndex = Math.ceil(finalOffsetX / slideWidth);
// Negative offsetX to slide correctly
content.style.webkitTransform = 'translate3d(' + -finalOffsetX + 'px, 0, 0)';
}
_this._initDrag();
});
},
/**
* Initialize a drag by grabbing the content area to drag, and any other
* info we might need for the dragging.
*/
_startDrag: function(e) {
var offsetX, content;
this._initDrag();
// Make sure to grab the element we will slide as our target
content = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'slide-box-slides');
if(!content) {
return;
}
// Disable transitions during drag
content.classList.remove('slide-box-animating');
// Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
offsetX = parseFloat(content.style.webkitTransform.replace('translate3d(', '').split(',')[0]) || 0;
this._drag = {
content: content,
startOffsetX: offsetX,
resist: 1
};
},
/**
* Process the drag event to move the item to the left or right.
*/
_handleDrag: function(e) {
var _this = this;
window.requestAnimationFrame(function() {
var content;
// We really aren't dragging
if(!_this._drag) {
_this._startDrag(e);
}
// Sanity
if(!_this._drag) { return; }
// Stop any default events during the drag
e.preventDefault();
// Check if we should start dragging. Check if we've dragged past the threshold.
if(!_this._isDragging && (Math.abs(e.gesture.deltaX) > _this.dragThresholdX)) {
_this._isDragging = true;
}
if(_this._isDragging) {
content = _this._drag.content;
var newX = _this._drag.startOffsetX + (e.gesture.deltaX / _this._drag.resist);
var rightMostX = -(content.offsetWidth * Math.max(0, content.children.length - 1));
if(newX > 0) {
// We are dragging past the leftmost pane, rubber band
_this._drag.resist = (newX / content.offsetWidth) + 1.4;
} else if(newX < rightMostX) {
// Dragging past the rightmost pane, rubber band
//newX = Math.min(rightMostX, + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
_this._drag.resist = (Math.abs(newX) / content.offsetWidth) - 0.6;
}
_this._drag.content.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)';
}
});
}
};
})(window.ionic);