UNPKG

gaf-mobile

Version:

GAF mobile Web site

596 lines (531 loc) 30.1 kB
(function() { "use strict"; angular.module('angular-carousel') .service('DeviceCapabilities', function() { // TODO: merge in a single function // detect supported CSS property function detectTransformProperty() { var transformProperty = 'transform', safariPropertyHack = 'webkitTransform'; if (typeof document.body.style[transformProperty] !== 'undefined') { ['webkit', 'moz', 'o', 'ms'].every(function (prefix) { var e = '-' + prefix + '-transform'; if (typeof document.body.style[e] !== 'undefined') { transformProperty = e; return false; } return true; }); } else if (typeof document.body.style[safariPropertyHack] !== 'undefined') { transformProperty = '-webkit-transform'; } else { transformProperty = undefined; } return transformProperty; } //Detect support of translate3d function detect3dSupport() { var el = document.createElement('p'), has3d, transforms = { 'webkitTransform': '-webkit-transform', 'msTransform': '-ms-transform', 'transform': 'transform' }; // Add it to the body to get the computed style document.body.insertBefore(el, null); for (var t in transforms) { if (el.style[t] !== undefined) { el.style[t] = 'translate3d(1px,1px,1px)'; has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); } } document.body.removeChild(el); return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); } return { has3d: detect3dSupport(), transformProperty: detectTransformProperty() }; }) .service('computeCarouselSlideStyle', function(DeviceCapabilities) { // compute transition transform properties for a given slide and global offset return function(slideIndex, offset, transitionType) { var style = { display: 'inline-block' }, opacity, absoluteLeft = (slideIndex * 100) + offset, slideTransformValue = DeviceCapabilities.has3d ? 'translate3d(' + absoluteLeft + '%, 0, 0)' : 'translate3d(' + absoluteLeft + '%, 0)', distance = ((100 - Math.abs(absoluteLeft)) / 100); if (!DeviceCapabilities.transformProperty) { // fallback to default slide if transformProperty is not available style['margin-left'] = absoluteLeft + '%'; } else { if (transitionType == 'fadeAndSlide') { style[DeviceCapabilities.transformProperty] = slideTransformValue; opacity = 0; if (Math.abs(absoluteLeft) < 100) { opacity = 0.3 + distance * 0.7; } style.opacity = opacity; } else if (transitionType == 'hexagon') { var transformFrom = 100, degrees = 0, maxDegrees = 60 * (distance - 1); transformFrom = offset < (slideIndex * -100) ? 100 : 0; degrees = offset < (slideIndex * -100) ? maxDegrees : -maxDegrees; style[DeviceCapabilities.transformProperty] = slideTransformValue + ' ' + 'rotateY(' + degrees + 'deg)'; style[DeviceCapabilities.transformProperty + '-origin'] = transformFrom + '% 50%'; } else if (transitionType == 'zoom') { style[DeviceCapabilities.transformProperty] = slideTransformValue; var scale = 1; if (Math.abs(absoluteLeft) < 100) { scale = 1 + ((1 - distance) * 2); } style[DeviceCapabilities.transformProperty] += ' scale(' + scale + ')'; style[DeviceCapabilities.transformProperty + '-origin'] = '50% 50%'; opacity = 0; if (Math.abs(absoluteLeft) < 100) { opacity = 0.3 + distance * 0.7; } style.opacity = opacity; } else { style[DeviceCapabilities.transformProperty] = slideTransformValue; } } return style; }; }) .service('createStyleString', function() { return function(object) { var styles = []; angular.forEach(object, function(value, key) { styles.push(key + ':' + value); }); return styles.join(';'); }; }) .directive('rnCarousel', ['$swipe', '$window', '$document', '$parse', '$compile', '$timeout', '$interval', 'computeCarouselSlideStyle', 'createStyleString', 'Tweenable', function($swipe, $window, $document, $parse, $compile, $timeout, $interval, computeCarouselSlideStyle, createStyleString, Tweenable) { // internal ids to allow multiple instances var carouselId = 0, // in absolute pixels, at which distance the slide stick to the edge on release rubberTreshold = 3; var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame; function getItemIndex(collection, target, defaultIndex) { var result = defaultIndex; collection.every(function(item, index) { if (angular.equals(item, target)) { result = index; return false; } return true; }); return result; } return { restrict: 'A', scope: true, compile: function(tElement, tAttributes) { // use the compile phase to customize the DOM var firstChild = tElement[0].querySelector('li'), firstChildAttributes = (firstChild) ? firstChild.attributes : [], isRepeatBased = false, isBuffered = false, repeatItem, repeatCollection; // try to find an ngRepeat expression // at this point, the attributes are not yet normalized so we need to try various syntax ['ng-repeat', 'data-ng-repeat', 'ng:repeat', 'x-ng-repeat'].every(function(attr) { var repeatAttribute = firstChildAttributes[attr]; if (angular.isDefined(repeatAttribute)) { // ngRepeat regexp extracted from angular 1.2.7 src var exprMatch = repeatAttribute.value.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), trackProperty = exprMatch[3]; repeatItem = exprMatch[1]; repeatCollection = exprMatch[2]; if (repeatItem) { if (angular.isDefined(tAttributes['rnCarouselBuffered'])) { // update the current ngRepeat expression and add a slice operator if buffered isBuffered = true; repeatAttribute.value = repeatItem + ' in ' + repeatCollection + '|carouselSlice:carouselBufferIndex:carouselBufferSize'; if (trackProperty) { repeatAttribute.value += ' track by ' + trackProperty; } } isRepeatBased = true; return false; } } return true; }); return function(scope, iElement, iAttributes, containerCtrl) { carouselId++; var defaultOptions = { transitionType: iAttributes.rnCarouselTransition || 'slide', transitionEasing: iAttributes.rnCarouselEasing || 'easeTo', transitionDuration: parseInt(iAttributes.rnCarouselDuration, 10) || 300, isSequential: true, autoSlideDuration: 3, bufferSize: 5, /* in container % how much we need to drag to trigger the slide change */ moveTreshold: 0.1, defaultIndex: 0 }; // TODO var options = angular.extend({}, defaultOptions); var pressed, startX, isIndexBound = false, offset = 0, destination, swipeMoved = false, //animOnIndexChange = true, currentSlides = [], elWidth = null, elX = null, animateTransitions = true, intialState = true, animating = false, mouseUpBound = false, locked = false; //rn-swipe-disabled =true will only disable swipe events if(iAttributes.rnSwipeDisabled !== "true") { $swipe.bind(iElement, { start: swipeStart, move: swipeMove, end: swipeEnd, cancel: function(event) { swipeEnd({}, event); } }); } function getSlidesDOM() { return iElement[0].querySelectorAll('ul[rn-carousel] > li'); } function documentMouseUpEvent(event) { // in case we click outside the carousel, trigger a fake swipeEnd swipeMoved = true; swipeEnd({ x: event.clientX, y: event.clientY }, event); } function updateSlidesPosition(offset) { // manually apply transformation to carousel childrens // todo : optim : apply only to visible items var x = scope.carouselBufferIndex * 100 + offset; angular.forEach(getSlidesDOM(), function(child, index) { child.style.cssText = createStyleString(computeCarouselSlideStyle(index, x, options.transitionType)); }); } scope.nextSlide = function(slideOptions) { var index = scope.carouselIndex + 1; if (index > currentSlides.length - 1) { index = 0; } if (!locked) { goToSlide(index, slideOptions); } }; scope.prevSlide = function(slideOptions) { var index = scope.carouselIndex - 1; if (index < 0) { index = currentSlides.length - 1; } goToSlide(index, slideOptions); }; function goToSlide(index, slideOptions) { //console.log('goToSlide', arguments); // move a to the given slide index if (index === undefined) { index = scope.carouselIndex; } slideOptions = slideOptions || {}; if (slideOptions.animate === false || options.transitionType === 'none') { locked = false; offset = index * -100; scope.carouselIndex = index; updateBufferIndex(); return; } locked = true; var tweenable = new Tweenable(); tweenable.tween({ from: { 'x': offset }, to: { 'x': index * -100 }, duration: options.transitionDuration, easing: options.transitionEasing, step: function(state) { updateSlidesPosition(state.x); }, finish: function() { scope.$apply(function() { scope.carouselIndex = index; offset = index * -100; updateBufferIndex(); $timeout(function () { locked = false; }, 0, false); }); } }); } function getContainerWidth() { var rect = iElement[0].getBoundingClientRect(); return rect.width ? rect.width : rect.right - rect.left; } function updateContainerWidth() { elWidth = getContainerWidth(); } function bindMouseUpEvent() { if (!mouseUpBound) { mouseUpBound = true; $document.bind('mouseup', documentMouseUpEvent); } } function unbindMouseUpEvent() { if (mouseUpBound) { mouseUpBound = false; $document.unbind('mouseup', documentMouseUpEvent); } } function swipeStart(coords, event) { // console.log('swipeStart', coords, event); if (locked || currentSlides.length <= 1) { return; } updateContainerWidth(); elX = iElement[0].querySelector('li').getBoundingClientRect().left; pressed = true; startX = coords.x; return false; } function swipeMove(coords, event) { //console.log('swipeMove', coords, event); var x, delta; bindMouseUpEvent(); if (pressed) { x = coords.x; delta = startX - x; if (delta > 2 || delta < -2) { swipeMoved = true; var moveOffset = offset + (-delta * 100 / elWidth); updateSlidesPosition(moveOffset); } } return false; } var init = true; scope.carouselIndex = 0; if (!isRepeatBased) { // fake array when no ng-repeat currentSlides = []; angular.forEach(getSlidesDOM(), function(node, index) { currentSlides.push({id: index}); }); } if (iAttributes.rnCarouselControls!==undefined) { // dont use a directive for this var canloop = ((isRepeatBased ? scope[repeatCollection.replace('::', '')].length : currentSlides.length) > 1) ? angular.isDefined(tAttributes['rnCarouselControlsAllowLoop']) : false; var nextSlideIndexCompareValue = isRepeatBased ? repeatCollection.replace('::', '') + '.length - 1' : currentSlides.length - 1; var tpl = '<div class="rn-carousel-controls">\n' + ' <span class="rn-carousel-control rn-carousel-control-prev" ng-click="prevSlide()" ng-if="carouselIndex > 0 || ' + canloop + '"></span>\n' + ' <span class="rn-carousel-control rn-carousel-control-next" ng-click="nextSlide()" ng-if="carouselIndex < ' + nextSlideIndexCompareValue + ' || ' + canloop + '"></span>\n' + '</div>'; iElement.parent().append($compile(angular.element(tpl))(scope)); } if (iAttributes.rnCarouselAutoSlide!==undefined) { var duration = parseInt(iAttributes.rnCarouselAutoSlide, 10) || options.autoSlideDuration; scope.autoSlide = function() { if (scope.autoSlider) { $interval.cancel(scope.autoSlider); scope.autoSlider = null; } scope.autoSlider = $interval(function() { if (!locked && !pressed) { scope.nextSlide(); } }, duration * 1000); }; } if (iAttributes.rnCarouselDefaultIndex) { var defaultIndexModel = $parse(iAttributes.rnCarouselDefaultIndex); options.defaultIndex = defaultIndexModel(scope.$parent) || 0; } if (iAttributes.rnCarouselIndex) { var updateParentIndex = function(value) { indexModel.assign(scope.$parent, value); }; var indexModel = $parse(iAttributes.rnCarouselIndex); if (angular.isFunction(indexModel.assign)) { /* check if this property is assignable then watch it */ scope.$watch('carouselIndex', function(newValue) { updateParentIndex(newValue); }); scope.$parent.$watch(indexModel, function(newValue, oldValue) { if (newValue !== undefined && newValue !== null) { if (currentSlides && currentSlides.length > 0 && newValue >= currentSlides.length) { newValue = currentSlides.length - 1; updateParentIndex(newValue); } else if (currentSlides && newValue < 0) { newValue = 0; updateParentIndex(newValue); } if (!locked) { goToSlide(newValue, { animate: !init }); } init = false; } }); isIndexBound = true; if (options.defaultIndex) { goToSlide(options.defaultIndex, { animate: !init }); } } else if (!isNaN(iAttributes.rnCarouselIndex)) { /* if user just set an initial number, set it */ goToSlide(parseInt(iAttributes.rnCarouselIndex, 10), { animate: false }); } } else { goToSlide(options.defaultIndex, { animate: !init }); init = false; } if (iAttributes.rnCarouselLocked) { scope.$watch(iAttributes.rnCarouselLocked, function(newValue, oldValue) { // only bind swipe when it's not switched off if(newValue === true) { locked = true; } else { locked = false; } }); } if (isRepeatBased) { // use rn-carousel-deep-watch to fight the Angular $watchCollection weakness : https://github.com/angular/angular.js/issues/2621 // optional because it have some performance impacts (deep watch) var deepWatch = (iAttributes.rnCarouselDeepWatch!==undefined); scope[deepWatch?'$watch':'$watchCollection'](repeatCollection, function(newValue, oldValue) { //console.log('repeatCollection', currentSlides); currentSlides = newValue; // if deepWatch ON ,manually compare objects to guess the new position if (deepWatch && angular.isArray(newValue)) { var activeElement = oldValue[scope.carouselIndex]; var newIndex = getItemIndex(newValue, activeElement, scope.carouselIndex); goToSlide(newIndex, {animate: false}); } else { goToSlide(scope.carouselIndex, {animate: false}); } }, true); } function swipeEnd(coords, event, forceAnimation) { // console.log('swipeEnd', 'scope.carouselIndex', scope.carouselIndex); // Prevent clicks on buttons inside slider to trigger "swipeEnd" event on touchend/mouseup // console.log(iAttributes.rnCarouselOnInfiniteScroll); if (event && !swipeMoved) { return; } unbindMouseUpEvent(); pressed = false; swipeMoved = false; destination = startX - coords.x; if (destination===0) { return; } if (locked) { return; } offset += (-destination * 100 / elWidth); if (options.isSequential) { var minMove = options.moveTreshold * elWidth, absMove = -destination, slidesMove = -Math[absMove >= 0 ? 'ceil' : 'floor'](absMove / elWidth), shouldMove = Math.abs(absMove) > minMove; if (currentSlides && (slidesMove + scope.carouselIndex) >= currentSlides.length) { slidesMove = currentSlides.length - 1 - scope.carouselIndex; } if ((slidesMove + scope.carouselIndex) < 0) { slidesMove = -scope.carouselIndex; } var moveOffset = shouldMove ? slidesMove : 0; destination = (scope.carouselIndex + moveOffset); goToSlide(destination); if(iAttributes.rnCarouselOnInfiniteScrollRight!==undefined && slidesMove === 0 && scope.carouselIndex !== 0) { $parse(iAttributes.rnCarouselOnInfiniteScrollRight)(scope) goToSlide(0); } if(iAttributes.rnCarouselOnInfiniteScrollLeft!==undefined && slidesMove === 0 && scope.carouselIndex === 0 && moveOffset === 0) { $parse(iAttributes.rnCarouselOnInfiniteScrollLeft)(scope) goToSlide(currentSlides.length); } } else { scope.$apply(function() { scope.carouselIndex = parseInt(-offset / 100, 10); updateBufferIndex(); }); } } scope.$on('$destroy', function() { unbindMouseUpEvent(); }); scope.carouselBufferIndex = 0; scope.carouselBufferSize = options.bufferSize; function updateBufferIndex() { // update and cap te buffer index var bufferIndex = 0; var bufferEdgeSize = (scope.carouselBufferSize - 1) / 2; if (isBuffered) { if (scope.carouselIndex <= bufferEdgeSize) { // first buffer part bufferIndex = 0; } else if (currentSlides && currentSlides.length < scope.carouselBufferSize) { // smaller than buffer bufferIndex = 0; } else if (currentSlides && scope.carouselIndex > currentSlides.length - scope.carouselBufferSize) { // last buffer part bufferIndex = currentSlides.length - scope.carouselBufferSize; } else { // compute buffer start bufferIndex = scope.carouselIndex - bufferEdgeSize; } scope.carouselBufferIndex = bufferIndex; $timeout(function() { updateSlidesPosition(offset); }, 0, false); } else { $timeout(function() { updateSlidesPosition(offset); }, 0, false); } } function onOrientationChange() { updateContainerWidth(); goToSlide(); } // handle orientation change var winEl = angular.element($window); winEl.bind('orientationchange', onOrientationChange); winEl.bind('resize', onOrientationChange); scope.$on('$destroy', function() { unbindMouseUpEvent(); winEl.unbind('orientationchange', onOrientationChange); winEl.unbind('resize', onOrientationChange); }); }; } }; } ]); })();