Much better slide box

This commit is contained in:
Max Lynch
2013-12-15 21:14:35 -06:00
parent 8bd8193c89
commit b9ebcd86a4
9 changed files with 1263 additions and 464 deletions

View File

@@ -30,7 +30,7 @@ module.exports = function(grunt) {
'js/views/navBarView.js',
'js/views/popupView.js',
'js/views/sideMenuView.js',
'js/views/slideBoxView.js',
'js/views/sliderView.js',
'js/views/tabBarView.js',
'js/views/toggleView.js',

38
dist/css/ionic.css vendored
View File

@@ -3,7 +3,7 @@
* Copyright 2013 Drifty Co.
* http://drifty.com/
*
* Ionic, v0.9.14
* Ionic, v0.9.17
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -4408,44 +4408,40 @@ button.item-button-right:after {
* Slide Box
* --------------------------------------------------
*/
.slide-box {
.slider {
position: relative;
overflow: hidden;
visibility: hidden;
background-color: #000; }
.slide-box-slides {
.slider-slides {
position: relative;
white-space: nowrap;
font-size: 0;
-webkit-transition: -webkit-transform 0 ease-in-out;
-moz-transition: -moz-transform 0 ease-in-out;
transition: transform 0 ease-in-out; }
font-size: 0; }
.slide-box-animating {
-webkit-transition-duration: 0.2s;
-moz-transition-duration: 0.2s;
transition-duration: 0.2s; }
.slide {
display: inline-block;
.slider-slide {
display: block;
position: relative;
width: 100%;
height: 100%;
float: left;
vertical-align: top; }
.slide img {
.slider-slide img {
width: 100%; }
.slide-box-pager {
.slider-pager {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center; }
.slide-box-pager > * {
text-align: center;
z-index: 1; }
.slider-pager .slider-pager-page {
display: inline-block;
margin: 0px 5px;
margin: 0px 3px;
width: 15px;
color: #fff;
text-decoration: none;
opacity: 0.3; }
.slide-box-pager > *.active {
.slider-pager .slider-pager-page.active {
opacity: 1;
-webkit-transition: opacity 0.4s ease-in;
-moz-transition: opacity 0.4s ease-in;

View File

@@ -2,7 +2,7 @@
* Copyright 2013 Drifty Co.
* http://drifty.com/
*
* Ionic, v0.9.14
* Ionic, v0.9.17
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -1811,32 +1811,38 @@ angular.module('ionic.ui.slideBox', [])
* some side menu stuff on the current scope.
*/
.directive('slideBox', ['$compile', function($compile) {
.directive('slideBox', ['$timeout', '$compile', function($timeout, $compile) {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {},
controller: ['$scope', '$element', function($scope, $element) {
$scope.slides = [];
this.slideAdded = function() {
$scope.slides.push({});
};
var _this = this;
angular.extend(this, ionic.views.SlideBox.prototype);
ionic.views.SlideBox.call(this, {
var slider = new ionic.views.Slider({
el: $element[0],
slideChanged: function(slideIndex) {
slidesChanged: function() {
$scope.currentSlide = slider.getPos();
$timeout(function() {});
},
callback: function(slideIndex) {
$scope.currentSlide = slideIndex;
$scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
$scope.$apply();
}
});
$scope.$parent.slideBox = this;
slider.load();
$scope.slider = slider;
$timeout(function() {
$scope.slider.setup();
});
}],
template: '<div class="slide-box">\
<div class="slide-box-slides" ng-transclude>\
template: '<div class="slider">\
<div class="slider-slides" ng-transclude>\
</div>\
</div>',
@@ -1844,8 +1850,9 @@ angular.module('ionic.ui.slideBox', [])
// If the pager should show, append it to the slide box
if($attr.showPager !== "false") {
var childScope = $scope.$new();
var pager = $compile('<pager></pager>')(childScope);
var pager = angular.element('<pager></pager>');
$element.append(pager);
$compile(pager)(childScope);
}
}
};
@@ -1857,10 +1864,9 @@ angular.module('ionic.ui.slideBox', [])
replace: true,
require: '^slideBox',
transclude: true,
template: '<div class="slide-box-slide" ng-transclude></div>',
template: '<div class="slider-slide" ng-transclude></div>',
compile: function(element, attr, transclude) {
return function($scope, $element, $attr, slideBoxCtrl) {
slideBoxCtrl.slideAdded();
};
}
};
@@ -1871,7 +1877,29 @@ angular.module('ionic.ui.slideBox', [])
restrict: 'E',
replace: true,
require: '^slideBox',
template: '<div class="slide-box-pager"><span ng-repeat="slide in slides"><i class="icon ion-record"></i></span></div>'
template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}"><i class="icon ion-record"></i></span></div>',
link: function($scope, $element, $attr, slideBox) {
var selectPage = function(index) {
var children = $element[0].children;
var length = children.length;
for(var i = 0; i < length; i++) {
if(i == index) {
children[i].classList.add('active');
} else {
children[i].classList.remove('active');
}
}
};
$scope.numSlides = function() {
return new Array($scope.slider.getNumSlides());
};
$scope.$watch('currentSlide', function(v) {
console.log('Current slide', v);
selectPage(v);
});
}
};
});

863
dist/js/ionic.js vendored
View File

@@ -2,7 +2,7 @@
* Copyright 2013 Drifty Co.
* http://drifty.com/
*
* Ionic, v0.9.14
* Ionic, v0.9.17
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
@@ -16,7 +16,7 @@
window.ionic = {
controllers: {},
views: {},
version: '0.9.14'
version: '0.9.17'
};;
(function(ionic) {
@@ -4931,342 +4931,587 @@ ionic.views.Scroll = ionic.views.View.inherit({
})(ionic);
;
/**
* The SlideBox is a swipeable, slidable, slideshowable box. Think of any image gallery
* or iOS "dot" pager gallery, or maybe a carousel.
/*
* Adapted from Swipe.js 2.0
*
* Each screen fills the full width and height of the viewport, and screens can
* be swiped between.
*/
* Brad Birdsall
* Copyright 2013, MIT License
*
*/
(function(ionic) {
'use strict';
ionic.views.SlideBox = ionic.views.View.inherit({
initialize: function(opts) {
var _this = this;
ionic.views.Slider = ionic.views.View.inherit({
initialize: function (options) {
// utilities
var noop = function() {}; // simple no operation function
var offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution
this.slideChanged = opts.slideChanged || function() {};
this.el = opts.el;
this.pager = this.el.querySelector('.slide-box-pager');
// check browser capabilities
var browser = {
addEventListener: !!window.addEventListener,
touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
transitions: (function(temp) {
var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
return false;
})(document.createElement('swipe'))
};
// 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();
var container = options.el;
// Listen for drag and release events
window.ionic.onGesture('drag', function(e) {
_this._handleDrag(e);
e.gesture.srcEvent.preventDefault();
}, this.el);
window.ionic.onGesture('release', function(e) {
_this._handleEndDrag(e);
}, this.el);
},
// quit if no root element
if (!container) return;
var element = container.children[0];
var slides, slidePos, width, length;
options = options || {};
var index = parseInt(options.startSlide, 10) || 0;
var speed = options.speed || 300;
options.continuous = options.continuous !== undefined ? options.continuous : true;
/**
* Tell the pager to update itself if content is added or
* removed.
*/
update: function() {
this._updatePager();
},
function setup() {
prependSlide: function(el) {
var content = this.el.firstElementChild;
if(!content) { return; }
// cache slides
slides = element.children;
length = slides.length;
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);
// set continuous to false if only one slide
if (slides.length < 2) options.continuous = false;
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;
//special case if two slides
if (browser.transitions && options.continuous && slides.length < 3) {
element.appendChild(slides[0].cloneNode(true));
element.appendChild(element.children[1].cloneNode(true));
slides = element.children;
}
// Get the width of one slide
var slideWidth = content.offsetWidth;
// create an array to store current positions of each slide
slidePos = new Array(slides.length);
// Calculate the new offsetX position which is just
// N slides to the left, where N is the given index
var offsetX = index * slideWidth;
// determine width of each slide
width = container.getBoundingClientRect().width || container.offsetWidth;
// 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;
element.style.width = (slides.length * width) + 'px';
// Bounds the offset X position in the range maxX >= offsetX >= 0
offsetX = offsetX < 0 ? 0 : offsetX > maxX ? maxX : offsetX;
// stack elements
var pos = slides.length;
while(pos--) {
// Animate and slide the slides over
content.classList.add('slide-box-animating');
content.style.webkitTransform = 'translate3d(' + -offsetX + 'px, 0, 0)';
var slide = slides[pos];
var lastSlide = this.slideIndex;
slide.style.width = width + 'px';
slide.setAttribute('data-index', pos);
// Update the slide index
this.slideIndex = Math.ceil(offsetX / slideWidth);
if (browser.transitions) {
slide.style.left = (pos * -width) + 'px';
move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
}
if(lastSlide !== this.slideIndex) {
this.slideChanged && this.slideChanged(this.slideIndex);
}
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;
// reposition elements before and after index
if (options.continuous && browser.transitions) {
move(circle(index-1), -width, 0);
move(circle(index+1), width, 0);
}
var numPagerChildren = this.pager.children.length;
if(!numPagerChildren) {
// No children to update
return;
}
if (!browser.transitions) element.style.left = (index * -width) + 'px';
// 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');
}
}
},
container.style.visibility = 'visible';
_initDrag: function() {
this._isDragging = false;
this._drag = null;
},
_handleEndDrag: function(e) {
var _this = this,
finalOffsetX, content, ratio, slideWidth, totalWidth, offsetX;
window.rAF(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.rAF(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)';
}
});
options.slidesChanged && options.slidesChanged();
}
});
})(window.ionic);
function prev() {
if (options.continuous) slide(index-1);
else if (index) slide(index-1);
}
function next() {
if (options.continuous) slide(index+1);
else if (index < slides.length - 1) slide(index+1);
}
function circle(index) {
// a simple positive modulo using slides.length
return (slides.length + (index % slides.length)) % slides.length;
}
function slide(to, slideSpeed) {
// do nothing if already on requested slide
if (index == to) return;
if (browser.transitions) {
var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward
// get the actual position of the slide
if (options.continuous) {
var natural_direction = direction;
direction = -slidePos[circle(to)] / width;
// if going forward but to < index, use to = slides.length + to
// if going backward but to > index, use to = -slides.length + to
if (direction !== natural_direction) to = -direction * slides.length + to;
}
var diff = Math.abs(index-to) - 1;
// move all the slides between index and to in the right direction
while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
to = circle(to);
move(index, width * direction, slideSpeed || speed);
move(to, 0, slideSpeed || speed);
if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
} else {
to = circle(to);
animate(index * -width, to * -width, slideSpeed || speed);
//no fallback for a circular continuous if the browser does not accept transitions
}
index = to;
offloadFn(options.callback && options.callback(index, slides[index]));
}
function move(index, dist, speed) {
translate(index, dist, speed);
slidePos[index] = dist;
}
function translate(index, dist, speed) {
var slide = slides[index];
var style = slide && slide.style;
if (!style) return;
style.webkitTransitionDuration =
style.MozTransitionDuration =
style.msTransitionDuration =
style.OTransitionDuration =
style.transitionDuration = speed + 'ms';
style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
style.msTransform =
style.MozTransform =
style.OTransform = 'translateX(' + dist + 'px)';
}
function animate(from, to, speed) {
// if not an animation, just reposition
if (!speed) {
element.style.left = to + 'px';
return;
}
var start = +new Date;
var timer = setInterval(function() {
var timeElap = +new Date - start;
if (timeElap > speed) {
element.style.left = to + 'px';
if (delay) begin();
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
clearInterval(timer);
return;
}
element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
}, 4);
}
// setup auto slideshow
var delay = options.auto || 0;
var interval;
function begin() {
interval = setTimeout(next, delay);
}
function stop() {
delay = 0;
clearTimeout(interval);
}
// setup initial vars
var start = {};
var delta = {};
var isScrolling;
// setup event capturing
var events = {
handleEvent: function(event) {
if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
event.touches = [{
pageX: event.pageX,
pageY: event.pageY
}];
}
switch (event.type) {
case 'mousedown': this.start(event); break;
case 'touchstart': this.start(event); break;
case 'touchmove': this.move(event); break;
case 'mousemove': this.move(event); break;
case 'touchend': offloadFn(this.end(event)); break;
case 'mouseup': offloadFn(this.end(event)); break;
case 'webkitTransitionEnd':
case 'msTransitionEnd':
case 'oTransitionEnd':
case 'otransitionend':
case 'transitionend': offloadFn(this.transitionEnd(event)); break;
case 'resize': offloadFn(setup); break;
}
if (options.stopPropagation) event.stopPropagation();
},
start: function(event) {
var touches = event.touches[0];
// measure start values
start = {
// get initial touch coords
x: touches.pageX,
y: touches.pageY,
// store time to determine touch duration
time: +new Date
};
// used for testing first move event
isScrolling = undefined;
// reset delta and end measurements
delta = {};
// attach touchmove and touchend listeners
if(browser.touch) {
element.addEventListener('touchmove', this, false);
element.addEventListener('touchend', this, false);
} else {
element.addEventListener('mousemove', this, false);
element.addEventListener('mouseup', this, false);
}
},
move: function(event) {
// ensure swiping with one touch and not pinching
if ( event.touches.length > 1 || event.scale && event.scale !== 1) return
if (options.disableScroll) event.preventDefault();
var touches = event.touches[0];
// measure change in x and y
delta = {
x: touches.pageX - start.x,
y: touches.pageY - start.y
}
// determine if scrolling test has run - one time test
if ( typeof isScrolling == 'undefined') {
isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
}
// if user is not trying to scroll vertically
if (!isScrolling) {
// prevent native scrolling
event.preventDefault();
// stop slideshow
stop();
// increase resistance if first or last slide
if (options.continuous) { // we don't add resistance at the end
translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
translate(index, delta.x + slidePos[index], 0);
translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);
} else {
delta.x =
delta.x /
( (!index && delta.x > 0 // if first slide and sliding left
|| index == slides.length - 1 // or if last slide and sliding right
&& delta.x < 0 // and if sliding at all
) ?
( Math.abs(delta.x) / width + 1 ) // determine resistance level
: 1 ); // no resistance if false
// translate 1:1
translate(index-1, delta.x + slidePos[index-1], 0);
translate(index, delta.x + slidePos[index], 0);
translate(index+1, delta.x + slidePos[index+1], 0);
}
}
},
end: function(event) {
// measure duration
var duration = +new Date - start.time;
// determine if slide attempt triggers next/prev slide
var isValidSlide =
Number(duration) < 250 // if slide duration is less than 250ms
&& Math.abs(delta.x) > 20 // and if slide amt is greater than 20px
|| Math.abs(delta.x) > width/2; // or if slide amt is greater than half the width
// determine if slide attempt is past start and end
var isPastBounds =
!index && delta.x > 0 // if first slide and slide amt is greater than 0
|| index == slides.length - 1 && delta.x < 0; // or if last slide and slide amt is less than 0
if (options.continuous) isPastBounds = false;
// determine direction of swipe (true:right, false:left)
var direction = delta.x < 0;
// if not scrolling vertically
if (!isScrolling) {
if (isValidSlide && !isPastBounds) {
if (direction) {
if (options.continuous) { // we need to get the next in this direction in place
move(circle(index-1), -width, 0);
move(circle(index+2), width, 0);
} else {
move(index-1, -width, 0);
}
move(index, slidePos[index]-width, speed);
move(circle(index+1), slidePos[circle(index+1)]-width, speed);
index = circle(index+1);
} else {
if (options.continuous) { // we need to get the next in this direction in place
move(circle(index+1), width, 0);
move(circle(index-2), -width, 0);
} else {
move(index+1, width, 0);
}
move(index, slidePos[index]+width, speed);
move(circle(index-1), slidePos[circle(index-1)]+width, speed);
index = circle(index-1);
}
options.callback && options.callback(index, slides[index]);
} else {
if (options.continuous) {
move(circle(index-1), -width, speed);
move(index, 0, speed);
move(circle(index+1), width, speed);
} else {
move(index-1, -width, speed);
move(index, 0, speed);
move(index+1, width, speed);
}
}
}
// kill touchmove and touchend event listeners until touchstart called again
if(browser.touch) {
element.removeEventListener('touchmove', events, false)
element.removeEventListener('touchend', events, false)
} else {
element.removeEventListener('mousemove', events, false)
element.removeEventListener('mouseup', events, false)
}
},
transitionEnd: function(event) {
if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
if (delay) begin();
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
}
}
}
// Public API
this.setup = function() {
setup();
};
this.slide = function(to, speed) {
// cancel slideshow
stop();
slide(to, speed);
};
this.prev = function() {
// cancel slideshow
stop();
prev();
};
this.next = function() {
// cancel slideshow
stop();
next();
};
this.stop = function() {
// cancel slideshow
stop();
};
this.getPos = function() {
// return current index position
return index;
};
this.getNumSlides = function() {
// return total number of slides
return length;
};
this.kill = function() {
// cancel slideshow
stop();
// reset element
element.style.width = '';
element.style.left = '';
// reset slides
var pos = slides.length;
while(pos--) {
var slide = slides[pos];
slide.style.width = '';
slide.style.left = '';
if (browser.transitions) translate(pos, 0, 0);
}
// removed event listeners
if (browser.addEventListener) {
// remove current event listeners
element.removeEventListener('touchstart', events, false);
element.removeEventListener('webkitTransitionEnd', events, false);
element.removeEventListener('msTransitionEnd', events, false);
element.removeEventListener('oTransitionEnd', events, false);
element.removeEventListener('otransitionend', events, false);
element.removeEventListener('transitionend', events, false);
window.removeEventListener('resize', events, false);
}
else {
window.onresize = null;
}
};
this.load = function() {
// trigger setup
setup();
// start auto slideshow if applicable
if (delay) begin();
// add event listeners
if (browser.addEventListener) {
// set touchstart event on element
if (browser.touch) {
element.addEventListener('touchstart', events, false);
} else {
element.addEventListener('mousedown', events, false);
}
if (browser.transitions) {
element.addEventListener('webkitTransitionEnd', events, false);
element.addEventListener('msTransitionEnd', events, false);
element.addEventListener('oTransitionEnd', events, false);
element.addEventListener('otransitionend', events, false);
element.addEventListener('transitionend', events, false);
}
// set resize event on window
window.addEventListener('resize', events, false);
} else {
window.onresize = function () { setup() }; // to play nice with old IE
}
}
}
});
})(ionic);
;
(function(ionic) {
'use strict';

View File

@@ -15,32 +15,38 @@ angular.module('ionic.ui.slideBox', [])
* some side menu stuff on the current scope.
*/
.directive('slideBox', ['$compile', function($compile) {
.directive('slideBox', ['$timeout', '$compile', function($timeout, $compile) {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {},
controller: ['$scope', '$element', function($scope, $element) {
$scope.slides = [];
this.slideAdded = function() {
$scope.slides.push({});
};
var _this = this;
angular.extend(this, ionic.views.SlideBox.prototype);
ionic.views.SlideBox.call(this, {
var slider = new ionic.views.Slider({
el: $element[0],
slideChanged: function(slideIndex) {
slidesChanged: function() {
$scope.currentSlide = slider.getPos();
$timeout(function() {});
},
callback: function(slideIndex) {
$scope.currentSlide = slideIndex;
$scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
$scope.$apply();
}
});
$scope.$parent.slideBox = this;
slider.load();
$scope.slider = slider;
$timeout(function() {
$scope.slider.setup();
});
}],
template: '<div class="slide-box">\
<div class="slide-box-slides" ng-transclude>\
template: '<div class="slider">\
<div class="slider-slides" ng-transclude>\
</div>\
</div>',
@@ -48,8 +54,9 @@ angular.module('ionic.ui.slideBox', [])
// If the pager should show, append it to the slide box
if($attr.showPager !== "false") {
var childScope = $scope.$new();
var pager = $compile('<pager></pager>')(childScope);
var pager = angular.element('<pager></pager>');
$element.append(pager);
$compile(pager)(childScope);
}
}
};
@@ -61,10 +68,9 @@ angular.module('ionic.ui.slideBox', [])
replace: true,
require: '^slideBox',
transclude: true,
template: '<div class="slide-box-slide" ng-transclude></div>',
template: '<div class="slider-slide" ng-transclude></div>',
compile: function(element, attr, transclude) {
return function($scope, $element, $attr, slideBoxCtrl) {
slideBoxCtrl.slideAdded();
};
}
};
@@ -75,7 +81,29 @@ angular.module('ionic.ui.slideBox', [])
restrict: 'E',
replace: true,
require: '^slideBox',
template: '<div class="slide-box-pager"><span ng-repeat="slide in slides"><i class="icon ion-record"></i></span></div>'
template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}"><i class="icon ion-record"></i></span></div>',
link: function($scope, $element, $attr, slideBox) {
var selectPage = function(index) {
var children = $element[0].children;
var length = children.length;
for(var i = 0; i < length; i++) {
if(i == index) {
children[i].classList.add('active');
} else {
children[i].classList.remove('active');
}
}
};
$scope.numSlides = function() {
return new Array($scope.slider.getNumSlides());
};
$scope.$watch('currentSlide', function(v) {
console.log('Current slide', v);
selectPage(v);
});
}
};
});

View File

@@ -1,78 +0,0 @@
<html ng-app="slideBoxTest">
<head>
<meta charset="utf-8">
<title>Sldie Box</title>
<!-- Sets initial viewport load and disables zooming -->
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="../../../../dist/css/ionic.css">
<script src="../../../../dist/js/ionic.js"></script>
<script src="../../../../dist/js/angular/angular.js"></script>
<script src="../../../../dist/js/angular/angular-animate.js"></script>
<script src="../../../../dist/js/angular/angular-route.js"></script>
<script src="../../../../dist/js/angular/angular-touch.js"></script>
<script src="../../../../dist/js/angular/angular-sanitize.js"></script>
<script src="../../../../dist/js/ionic-angular.js"></script>
<style>
.box {
width: 100%;
height: 100%;
color: #fff;
}
.box h1 {
position: absolute;
top: 50%;
width: 100%;
margin-top: -50px;
color: #fff;
font-size: 60px;
text-align: center;
font-weight: bold;
}
.blue {
background-color: rgb(71, 138, 238);
}
.yellow {
background-color: rgb(233, 233, 109);
}
.pink {
background-color: rgb(233, 109, 233);
}
</style>
</head>
<body>
<div ng-controller="SlideCtrl">
<scroll direction="x" paging="true" on-scroll="onScroll(scrollTop, scrollLeft)">
<div class="slide">
<div class="box blue">
<h1>BLUE {{slideBox.slideIndex}}</h1>
</div>
</div>
<div class="slide">
<div class="box yellow">
<h1>YELLOW {{slideBox.slideIndex}}</h1>
</div>
</div>
<div class="slide">
<div class="box pink"><h1>PINK {{slideBox.slideIndex}}</h1></div>
</div>
</scroll>
</div>
<script>
angular.module('slideBoxTest', ['ionic'])
.controller('SlideCtrl', function($scope) {
$scope.onScroll = function(top, left) {
console.log('On scroll', top, left, $scope.scrollView.__maxScrollLeft);
};
$scope.$on('slideBox.slideChanged', function(e, index) {
console.log('Slide changed', index);
});
});
</script>
</body>
</html>

View File

@@ -1,7 +1,7 @@
<html ng-app="slideBoxTest">
<head>
<meta charset="utf-8">
<title>Sldie Box</title>
<title>Slide Box</title>
<!-- Sets initial viewport load and disables zooming -->
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
@@ -45,21 +45,21 @@
<body>
<div ng-controller="SlideCtrl">
<scroll direction="x" paging="true" on-scroll="onScroll(scrollTop, scrollLeft)">
<div class="slide">
<slide-box>
<slide>
<div class="box blue">
<h1>BLUE {{slideBox.slideIndex}}</h1>
</div>
</div>
<div class="slide">
</slide>
<slide>
<div class="box yellow">
<h1>YELLOW {{slideBox.slideIndex}}</h1>
</div>
</div>
<div class="slide">
</slide>
<slide>
<div class="box pink"><h1>PINK {{slideBox.slideIndex}}</h1></div>
</div>
</scroll>
</slide>
</slide-box>
</div>
<script>
angular.module('slideBoxTest', ['ionic'])

581
js/views/sliderView.js Normal file
View File

@@ -0,0 +1,581 @@
/*
* Adapted from Swipe.js 2.0
*
* Brad Birdsall
* Copyright 2013, MIT License
*
*/
(function(ionic) {
'use strict';
ionic.views.Slider = ionic.views.View.inherit({
initialize: function (options) {
// utilities
var noop = function() {}; // simple no operation function
var offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution
// check browser capabilities
var browser = {
addEventListener: !!window.addEventListener,
touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
transitions: (function(temp) {
var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
return false;
})(document.createElement('swipe'))
};
var container = options.el;
// quit if no root element
if (!container) return;
var element = container.children[0];
var slides, slidePos, width, length;
options = options || {};
var index = parseInt(options.startSlide, 10) || 0;
var speed = options.speed || 300;
options.continuous = options.continuous !== undefined ? options.continuous : true;
function setup() {
// cache slides
slides = element.children;
length = slides.length;
// set continuous to false if only one slide
if (slides.length < 2) options.continuous = false;
//special case if two slides
if (browser.transitions && options.continuous && slides.length < 3) {
element.appendChild(slides[0].cloneNode(true));
element.appendChild(element.children[1].cloneNode(true));
slides = element.children;
}
// create an array to store current positions of each slide
slidePos = new Array(slides.length);
// determine width of each slide
width = container.getBoundingClientRect().width || container.offsetWidth;
element.style.width = (slides.length * width) + 'px';
// stack elements
var pos = slides.length;
while(pos--) {
var slide = slides[pos];
slide.style.width = width + 'px';
slide.setAttribute('data-index', pos);
if (browser.transitions) {
slide.style.left = (pos * -width) + 'px';
move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
}
}
// reposition elements before and after index
if (options.continuous && browser.transitions) {
move(circle(index-1), -width, 0);
move(circle(index+1), width, 0);
}
if (!browser.transitions) element.style.left = (index * -width) + 'px';
container.style.visibility = 'visible';
options.slidesChanged && options.slidesChanged();
}
function prev() {
if (options.continuous) slide(index-1);
else if (index) slide(index-1);
}
function next() {
if (options.continuous) slide(index+1);
else if (index < slides.length - 1) slide(index+1);
}
function circle(index) {
// a simple positive modulo using slides.length
return (slides.length + (index % slides.length)) % slides.length;
}
function slide(to, slideSpeed) {
// do nothing if already on requested slide
if (index == to) return;
if (browser.transitions) {
var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward
// get the actual position of the slide
if (options.continuous) {
var natural_direction = direction;
direction = -slidePos[circle(to)] / width;
// if going forward but to < index, use to = slides.length + to
// if going backward but to > index, use to = -slides.length + to
if (direction !== natural_direction) to = -direction * slides.length + to;
}
var diff = Math.abs(index-to) - 1;
// move all the slides between index and to in the right direction
while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
to = circle(to);
move(index, width * direction, slideSpeed || speed);
move(to, 0, slideSpeed || speed);
if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
} else {
to = circle(to);
animate(index * -width, to * -width, slideSpeed || speed);
//no fallback for a circular continuous if the browser does not accept transitions
}
index = to;
offloadFn(options.callback && options.callback(index, slides[index]));
}
function move(index, dist, speed) {
translate(index, dist, speed);
slidePos[index] = dist;
}
function translate(index, dist, speed) {
var slide = slides[index];
var style = slide && slide.style;
if (!style) return;
style.webkitTransitionDuration =
style.MozTransitionDuration =
style.msTransitionDuration =
style.OTransitionDuration =
style.transitionDuration = speed + 'ms';
style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
style.msTransform =
style.MozTransform =
style.OTransform = 'translateX(' + dist + 'px)';
}
function animate(from, to, speed) {
// if not an animation, just reposition
if (!speed) {
element.style.left = to + 'px';
return;
}
var start = +new Date;
var timer = setInterval(function() {
var timeElap = +new Date - start;
if (timeElap > speed) {
element.style.left = to + 'px';
if (delay) begin();
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
clearInterval(timer);
return;
}
element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
}, 4);
}
// setup auto slideshow
var delay = options.auto || 0;
var interval;
function begin() {
interval = setTimeout(next, delay);
}
function stop() {
delay = 0;
clearTimeout(interval);
}
// setup initial vars
var start = {};
var delta = {};
var isScrolling;
// setup event capturing
var events = {
handleEvent: function(event) {
if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
event.touches = [{
pageX: event.pageX,
pageY: event.pageY
}];
}
switch (event.type) {
case 'mousedown': this.start(event); break;
case 'touchstart': this.start(event); break;
case 'touchmove': this.move(event); break;
case 'mousemove': this.move(event); break;
case 'touchend': offloadFn(this.end(event)); break;
case 'mouseup': offloadFn(this.end(event)); break;
case 'webkitTransitionEnd':
case 'msTransitionEnd':
case 'oTransitionEnd':
case 'otransitionend':
case 'transitionend': offloadFn(this.transitionEnd(event)); break;
case 'resize': offloadFn(setup); break;
}
if (options.stopPropagation) event.stopPropagation();
},
start: function(event) {
var touches = event.touches[0];
// measure start values
start = {
// get initial touch coords
x: touches.pageX,
y: touches.pageY,
// store time to determine touch duration
time: +new Date
};
// used for testing first move event
isScrolling = undefined;
// reset delta and end measurements
delta = {};
// attach touchmove and touchend listeners
if(browser.touch) {
element.addEventListener('touchmove', this, false);
element.addEventListener('touchend', this, false);
} else {
element.addEventListener('mousemove', this, false);
element.addEventListener('mouseup', this, false);
}
},
move: function(event) {
// ensure swiping with one touch and not pinching
if ( event.touches.length > 1 || event.scale && event.scale !== 1) return
if (options.disableScroll) event.preventDefault();
var touches = event.touches[0];
// measure change in x and y
delta = {
x: touches.pageX - start.x,
y: touches.pageY - start.y
}
// determine if scrolling test has run - one time test
if ( typeof isScrolling == 'undefined') {
isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
}
// if user is not trying to scroll vertically
if (!isScrolling) {
// prevent native scrolling
event.preventDefault();
// stop slideshow
stop();
// increase resistance if first or last slide
if (options.continuous) { // we don't add resistance at the end
translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
translate(index, delta.x + slidePos[index], 0);
translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);
} else {
delta.x =
delta.x /
( (!index && delta.x > 0 // if first slide and sliding left
|| index == slides.length - 1 // or if last slide and sliding right
&& delta.x < 0 // and if sliding at all
) ?
( Math.abs(delta.x) / width + 1 ) // determine resistance level
: 1 ); // no resistance if false
// translate 1:1
translate(index-1, delta.x + slidePos[index-1], 0);
translate(index, delta.x + slidePos[index], 0);
translate(index+1, delta.x + slidePos[index+1], 0);
}
}
},
end: function(event) {
// measure duration
var duration = +new Date - start.time;
// determine if slide attempt triggers next/prev slide
var isValidSlide =
Number(duration) < 250 // if slide duration is less than 250ms
&& Math.abs(delta.x) > 20 // and if slide amt is greater than 20px
|| Math.abs(delta.x) > width/2; // or if slide amt is greater than half the width
// determine if slide attempt is past start and end
var isPastBounds =
!index && delta.x > 0 // if first slide and slide amt is greater than 0
|| index == slides.length - 1 && delta.x < 0; // or if last slide and slide amt is less than 0
if (options.continuous) isPastBounds = false;
// determine direction of swipe (true:right, false:left)
var direction = delta.x < 0;
// if not scrolling vertically
if (!isScrolling) {
if (isValidSlide && !isPastBounds) {
if (direction) {
if (options.continuous) { // we need to get the next in this direction in place
move(circle(index-1), -width, 0);
move(circle(index+2), width, 0);
} else {
move(index-1, -width, 0);
}
move(index, slidePos[index]-width, speed);
move(circle(index+1), slidePos[circle(index+1)]-width, speed);
index = circle(index+1);
} else {
if (options.continuous) { // we need to get the next in this direction in place
move(circle(index+1), width, 0);
move(circle(index-2), -width, 0);
} else {
move(index+1, width, 0);
}
move(index, slidePos[index]+width, speed);
move(circle(index-1), slidePos[circle(index-1)]+width, speed);
index = circle(index-1);
}
options.callback && options.callback(index, slides[index]);
} else {
if (options.continuous) {
move(circle(index-1), -width, speed);
move(index, 0, speed);
move(circle(index+1), width, speed);
} else {
move(index-1, -width, speed);
move(index, 0, speed);
move(index+1, width, speed);
}
}
}
// kill touchmove and touchend event listeners until touchstart called again
if(browser.touch) {
element.removeEventListener('touchmove', events, false)
element.removeEventListener('touchend', events, false)
} else {
element.removeEventListener('mousemove', events, false)
element.removeEventListener('mouseup', events, false)
}
},
transitionEnd: function(event) {
if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
if (delay) begin();
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
}
}
}
// Public API
this.setup = function() {
setup();
};
this.slide = function(to, speed) {
// cancel slideshow
stop();
slide(to, speed);
};
this.prev = function() {
// cancel slideshow
stop();
prev();
};
this.next = function() {
// cancel slideshow
stop();
next();
};
this.stop = function() {
// cancel slideshow
stop();
};
this.getPos = function() {
// return current index position
return index;
};
this.getNumSlides = function() {
// return total number of slides
return length;
};
this.kill = function() {
// cancel slideshow
stop();
// reset element
element.style.width = '';
element.style.left = '';
// reset slides
var pos = slides.length;
while(pos--) {
var slide = slides[pos];
slide.style.width = '';
slide.style.left = '';
if (browser.transitions) translate(pos, 0, 0);
}
// removed event listeners
if (browser.addEventListener) {
// remove current event listeners
element.removeEventListener('touchstart', events, false);
element.removeEventListener('webkitTransitionEnd', events, false);
element.removeEventListener('msTransitionEnd', events, false);
element.removeEventListener('oTransitionEnd', events, false);
element.removeEventListener('otransitionend', events, false);
element.removeEventListener('transitionend', events, false);
window.removeEventListener('resize', events, false);
}
else {
window.onresize = null;
}
};
this.load = function() {
// trigger setup
setup();
// start auto slideshow if applicable
if (delay) begin();
// add event listeners
if (browser.addEventListener) {
// set touchstart event on element
if (browser.touch) {
element.addEventListener('touchstart', events, false);
} else {
element.addEventListener('mousedown', events, false);
}
if (browser.transitions) {
element.addEventListener('webkitTransitionEnd', events, false);
element.addEventListener('msTransitionEnd', events, false);
element.addEventListener('oTransitionEnd', events, false);
element.addEventListener('otransitionend', events, false);
element.addEventListener('transitionend', events, false);
}
// set resize event on window
window.addEventListener('resize', events, false);
} else {
window.onresize = function () { setup() }; // to play nice with old IE
}
}
}
});
})(ionic);

View File

@@ -4,27 +4,24 @@
* --------------------------------------------------
*/
.slide-box {
.slider {
position: relative;
// Make sure items don't scroll over ever
overflow: hidden;
visibility: hidden;
background-color: #000;
}
.slide-box-slides {
.slider-slides {
position: relative;
white-space: nowrap;
font-size: 0; // Remove the gaps between slide content items
@include transition-transform(0 ease-in-out);
}
.slide-box-animating {
@include transition-duration(0.2s);
}
.slide {
display: inline-block;
.slider-slide {
display: block;
position: relative;
width: 100%;
height: 100%;
float: left;
vertical-align: top;
img {
@@ -32,15 +29,17 @@
}
}
.slide-box-pager {
.slider-pager {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
z-index: 1;
> * {
.slider-pager-page {
display: inline-block;
margin: 0px 5px;
margin: 0px 3px;
width: 15px;
color: #fff;
text-decoration: none;