diff --git a/.gitignore b/.gitignore index 726c3ca..41b70a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .*.sw* +/.idea diff --git a/README.md b/README.md index e28bdb6..a18a9dc 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,59 @@ ionic-contrib-drawer ==================== A side menu drawer for Ionic apps + +#####Setting up: + +Clone the ionic-ion-drawer repo into the lib folder in your ionic project so it looks like: + +*yourProjectName/www/lib/ionic-ion-drawer* + +Add the ionic-contrib-drawer.js and ionic-contrib-drawer.css files to your index file + + + + +Add ionic.contrib.drawer to your Angular app: + + angular.module('App', [ + 'ionic', + 'ionic.contrib.drawer' + ]) + +#####Usage: + + Add the `` directive to your ionic templates or index.html files. + +#####Example: + + + +
+

Example

+
+
+ + +

Menu

+ + + Friends + Favorites + Search + +
+
+
+ + + +Change the 'side' attribute of the `` to either "right" or "left" to switch the side the drawer opens from. + +#####Credits: + +driftyco: https://github.com/driftyco (original build) + +vitalyrotari: https://github.com/vitalyrotari (bug fixes, improved performance, background fading +transition) + +brybott: https://github.com/brybott (right side functionality) diff --git a/index.html b/index.html index 0382143..1df22ca 100644 --- a/index.html +++ b/index.html @@ -4,15 +4,10 @@ Ionic Seed App - - - - + - - - + - diff --git a/ionic.contrib.drawer.css b/ionic.contrib.drawer.css index 37a450c..635c2c4 100644 --- a/ionic.contrib.drawer.css +++ b/ionic.contrib.drawer.css @@ -8,8 +8,8 @@ drawer { } drawer.animate { - -webkit-transition: 0.4s all ease-in-out; - transition: 0.4s all ease-in-out; + -webkit-transition: -webkit-transform 0.4s ease-in-out; + transition: transform 0.4s ease-in-out; } drawer.left { @@ -17,6 +17,7 @@ drawer.left { transform: translate3d(-100%, 0, 0); box-shadow: 1px 0px 10px rgba(0,0,0,0.3); } + drawer.right { right: 0; top: 0; @@ -24,3 +25,21 @@ drawer.right { transform: translate3d(100%, 0, 0); box-shadow: -1px 0px 10px rgba(0,0,0,0.3); } + +.drawer-overlay { + background-color: rgba(0, 0, 0, 0.6); + opacity: 0; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 99; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); +} + +.drawer-overlay.animate { + -webkit-transition: 0.4s opacity ease-in-out; + transition: 0.4s opacity ease-in-out; +} diff --git a/ionic.contrib.drawer.js b/ionic.contrib.drawer.js index b2f5815..c1e031e 100644 --- a/ionic.contrib.drawer.js +++ b/ionic.contrib.drawer.js @@ -9,179 +9,300 @@ */ angular.module('ionic.contrib.drawer', ['ionic']) -.controller('drawerCtrl', ['$element', '$attrs', '$ionicGesture', '$document', function($element, $attr, $ionicGesture, $document) { +.controller('drawerCtrl', ['$element', '$attrs', '$ionicGesture', '$document', '$ionicPlatform', function($element, $attr, $ionicGesture, $document, $ionicPlatform) { var el = $element[0]; var dragging = false; var startX, lastX, offsetX, newX; - var side; // How far to drag before triggering var thresholdX = 15; // How far from edge before triggering var edgeX = 40; - var LEFT = 0; - var RIGHT = 1; + var SIDE_LEFT = 'left'; + var SIDE_RIGHT = 'right'; + var STATE_CLOSE = 'close'; + var STATE_OPEN = 'open'; var isTargetDrag = false; - var width = $element[0].clientWidth; + var side = $attr.side === SIDE_LEFT ? SIDE_LEFT : SIDE_RIGHT; + var width = el.clientWidth; + var docWidth = $document[0].body.clientWidth; + console.log(docWidth) + + // Handle back button + var unregisterBackAction; + + // Current State of Drawer + var drawerState = STATE_CLOSE; + + // Drawer overlay + var $overlay = angular.element('
'); + var overlayEl = $overlay[0]; + var overlayState = STATE_CLOSE; + + $element.parent().prepend(overlayEl); + + var toggleOverlay = function(state) { + if (overlayState !== state) { + ionic.requestAnimationFrame(function() { + var translateX = state === STATE_CLOSE ? '-100' : '0'; + overlayEl.style[ionic.CSS.TRANSFORM] = 'translate3d(' + translateX + '%, 0, 0)'; + }); + overlayState = state; + } + }; var enableAnimation = function() { $element.addClass('animate'); + $overlay.addClass('animate'); }; + var disableAnimation = function() { $element.removeClass('animate'); + $overlay.removeClass('animate'); }; // Check if this is on target or not - var isTarget = function(el) { - while(el) { - if(el === $element[0]) { + var isTarget = function(targetEl) { + while (targetEl) { + if (targetEl === el) { return true; } - el = el.parentNode; + targetEl = targetEl.parentNode; } }; + var isOpen = function() { + return drawerState === STATE_OPEN; + }; + var startDrag = function(e) { disableAnimation(); + toggleOverlay(STATE_OPEN); dragging = true; offsetX = lastX - startX; - console.log('Starting drag'); - console.log('Offset:', offsetX); }; var startTargetDrag = function(e) { disableAnimation(); + toggleOverlay(STATE_OPEN); dragging = true; isTargetDrag = true; offsetX = lastX - startX; - console.log('Starting target drag'); - console.log('Offset:', offsetX); }; var doEndDrag = function(e) { - startX = null; - lastX = null; - offsetX = null; + startX = lastX = offsetX = null; isTargetDrag = false; - if(!dragging) { + if (!dragging) { return; } dragging = false; - console.log('End drag'); enableAnimation(); - ionic.requestAnimationFrame(function() { - if(newX < (-width / 2)) { - el.style.transform = el.style.webkitTransform = 'translate3d(' + -width + 'px, 0, 0)'; + var translateX = 0; + var opacity = 0; + + if (side === SIDE_RIGHT){ + if (newX > width / 2) { + translateX = width; + drawerState = STATE_CLOSE; + } else { + opacity = 1; + drawerState = STATE_OPEN; + } + } else if (side === SIDE_LEFT){ + if (newX < (-width / 2)) { + translateX = -width; + drawerState = STATE_CLOSE; } else { - el.style.transform = el.style.webkitTransform = 'translate3d(0px, 0, 0)'; + opacity = 1; + drawerState = STATE_OPEN; } + } + + toggleOverlay(drawerState); + + ionic.requestAnimationFrame(function() { + overlayEl.style.opacity = opacity; + el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + translateX + 'px, 0, 0)'; }); }; var doDrag = function(e) { - if(e.defaultPrevented) { + if (e.defaultPrevented) { return; } + + var finger = e.gesture.touches[0]; + var dir = e.gesture.direction; - if(!lastX) { - startX = e.gesture.touches[0].pageX; + if (!lastX) { + startX = finger.pageX; } - lastX = e.gesture.touches[0].pageX; - - if(!dragging) { + lastX = finger.pageX; + + if (dir === 'down' || dir === 'up') { + return; + } + if (!dragging) { + //here at just the beginning of drag // Dragged 15 pixels and finger is by edge - if(Math.abs(lastX - startX) > thresholdX) { - if(isTarget(e.target)) { + if (Math.abs(lastX - startX) > thresholdX) { + if (side === SIDE_LEFT){ + if (isOpen()) { + if (dir === SIDE_RIGHT) { + return; + } + } else { + if (dir === SIDE_LEFT) { + return; + } + } + } else if (side === SIDE_RIGHT){ + if (isOpen()) { + if (dir === SIDE_LEFT) { + return; + } + } else { + if (dir === SIDE_RIGHT) { + return; + } + } + } + + // Menu is open and drag has reached target + if (e.gesture.center.pageX < width && isOpen()) { startTargetDrag(e); - } else if(startX < edgeX) { + } else if((startX < edgeX && side === SIDE_LEFT) || (startX > docWidth-edgeX && side === SIDE_RIGHT)) { startDrag(e); } } } else { - console.log(lastX, offsetX, lastX - offsetX); - newX = Math.min(0, (-width + (lastX - offsetX))); - ionic.requestAnimationFrame(function() { - el.style.transform = el.style.webkitTransform = 'translate3d(' + newX + 'px, 0, 0)'; - }); + //here when we are dragging + e.gesture.srcEvent.stopImmediatePropagation(); + + // if fast gesture + if (e.gesture.deltaTime < 200) { + if (side === SIDE_LEFT){ + if (isOpen()) { + if (dir === SIDE_LEFT) { + return newX = -width; + } + } else { + if (dir === SIDE_RIGHT) { + return newX = 0; + } + } + } else if (side === SIDE_RIGHT){ + if (isOpen()) { + if (dir === SIDE_RIGHT) { + return newX = width; + } + } else { + if (dir === SIDE_LEFT) { + return newX = 0; + } + } + } + } + + if (side === SIDE_LEFT){ + newX = Math.min(0, (-width + (lastX - offsetX))); + var opacity = 1 + (newX / width); + } else if (side === SIDE_RIGHT){ + newX = Math.max(0, (width - (docWidth - lastX + offsetX))); + var opacity = 1 - (newX / width); + } + + if (opacity < 0) { + opacity = 0; + } + + ionic.requestAnimationFrame(function() { + overlayEl.style.opacity = opacity; + el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)'; + }); } - if(dragging) { + if (dragging) { e.gesture.srcEvent.preventDefault(); } }; - - side = $attr.side == 'left' ? LEFT : RIGHT; - console.log(side); - - $ionicGesture.on('drag', function(e) { - doDrag(e); - }, $document); - $ionicGesture.on('dragend', function(e) { - doEndDrag(e); - }, $document); - - + + var hardwareBackCallback = function() { + this.close(); + }.bind(this); + this.close = function() { + drawerState = STATE_CLOSE; enableAnimation(); + toggleOverlay(STATE_CLOSE); + ionic.requestAnimationFrame(function() { - if(side === LEFT) { - el.style.transform = el.style.webkitTransform = 'translate3d(-100%, 0, 0)'; - } else { - el.style.transform = el.style.webkitTransform = 'translate3d(100%, 0, 0)'; - } + overlayEl.style.opacity = 0; + el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + (side === SIDE_LEFT ? '-' : '') + '100%, 0, 0)'; }); + + if (unregisterBackAction) { + unregisterBackAction(); + } }; this.open = function() { + drawerState = STATE_OPEN; enableAnimation(); + toggleOverlay(STATE_OPEN); ionic.requestAnimationFrame(function() { - if(side === LEFT) { - el.style.transform = el.style.webkitTransform = 'translate3d(0%, 0, 0)'; - } else { - el.style.transform = el.style.webkitTransform = 'translate3d(0%, 0, 0)'; - } + overlayEl.style.opacity = 1; + el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, 0, 0)'; }); + + unregisterBackAction = $ionicPlatform.registerBackButtonAction(hardwareBackCallback, 100); }; + + this.isOpen = isOpen; + + $ionicGesture.on('drag', doDrag, $document); + $ionicGesture.on('dragend', doEndDrag, $document); + $overlay.on('click', this.close); }]) .directive('drawer', ['$rootScope', '$ionicGesture', function($rootScope, $ionicGesture) { return { restrict: 'E', controller: 'drawerCtrl', + scope: { + side: '=side' + }, link: function($scope, $element, $attr, ctrl) { $element.addClass($attr.side); + $scope.openDrawer = function() { - console.log('open'); ctrl.open(); }; + $scope.closeDrawer = function() { - console.log('close'); ctrl.close(); }; - } - } -}]); - -.directive('drawerClose', ['$rootScope', function($rootScope) { - return { - restrict: 'A', - link: function($scope, $element) { - $element.bind('click', function() { - var drawerCtrl = $element.inheritedData('$drawerController'); - drawerCtrl.close(); - }); + + $scope.toggleDrawer = function() { + if (ctrl.isOpen()) { + ctrl.close(); + } else { + ctrl.open(); + } + }; } } }]);