Skip to content

Commit

Permalink
feat(sidenav): add gestures to sidenav to allow dragging the sidenav
Browse files Browse the repository at this point in the history
  • Loading branch information
devversion authored and marekmicek committed Mar 17, 2018
1 parent e519c1b commit 863fb62
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 2 deletions.
73 changes: 73 additions & 0 deletions src/components/sidenav/demoSidenavDragging/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

<div ng-controller="AppCtrl" layout="column" style="height:500px;" ng-cloak>

<section layout="row" flex>

<md-sidenav class="md-sidenav-left md-whiteframe-z2" md-component-id="left">

<md-toolbar class="md-theme-light">
<h1 class="md-toolbar-tools">Drag Enabled</h1>
</md-toolbar>
<md-content ng-controller="LeftCtrl" layout-padding>
<p>
This sidenav is able to be dragged. Through the drag gesture, it's possible to close the sidenav.
</p>
<md-button ng-click="close()" class="md-primary">
Close Sidenav Left
</md-button>
</md-content>

</md-sidenav>

<md-content flex layout-padding>

<div layout="column" layout-fill layout-align="top center">
<p>
You can simply close a sidenav by using a gesture. Simply drag the sidenav to close it.
</p>
<p>
The right sidenav allows dragging.
</p>
<p>
The left sidenav is disabled for dragging.
</p>

<div>
<md-button ng-click="toggleLeft()"
class="md-primary">
Toggle left
</md-button>
</div>

<div>
<md-button ng-click="toggleRight()"
ng-hide="isOpenRight()"
class="md-primary">
Toggle right
</md-button>
</div>
</div>

<div flex></div>

</md-content>

<md-sidenav class="md-sidenav-right md-whiteframe-z2" md-component-id="right" md-disable-drag="true">

<md-toolbar class="md-theme-indigo">
<h1 class="md-toolbar-tools">Drag Disabled</h1>
</md-toolbar>
<md-content layout-padding ng-controller="RightCtrl">
<md-button ng-click="close()" class="md-primary">
Close Sidenav Right
</md-button>
<p>
This sidenav disables the dragging gesture. It can be closed through the buttons.
</p>
</md-content>

</md-sidenav>

</section>

</div>
27 changes: 27 additions & 0 deletions src/components/sidenav/demoSidenavDragging/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
angular
.module('sidenavDemo2', ['ngMaterial'])
.controller('AppCtrl', function ($scope, $timeout, $mdSidenav) {
$scope.toggleLeft = buildToggler('left');
$scope.toggleRight = buildToggler('right');

$scope.isOpenRight = function(){
return $mdSidenav('right').isOpen();
};

function buildToggler(navID) {
return function() {
$mdSidenav(navID).toggle()
}
}
})
.controller('LeftCtrl', function ($scope, $timeout, $mdSidenav) {
$scope.close = function () {
$mdSidenav('left').close();

};
})
.controller('RightCtrl', function ($scope, $timeout, $mdSidenav) {
$scope.close = function () {
$mdSidenav('right').close();
};
});
100 changes: 98 additions & 2 deletions src/components/sidenav/sidenav.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,7 @@ function SidenavFocusDirective() {
* - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
*/
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInteraction, $animate,
$compile, $parse, $log, $q, $document, $window, $$rAF) {
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $mdGesture, $parse, $log, $q, $document, $timeout, $mdInteraction, $compile, $window, $$rAF) {
return {
restrict: 'E',
scope: {
Expand Down Expand Up @@ -322,6 +321,8 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInterac
scope.$watch(isLocked, updateIsLocked);
scope.$watch('isOpen', updateIsOpen);

// Enable dragging
if (!angular.isDefined(attr.mdDisableDrag) || !attr.mdDisableDrag) enableDragging();

// Publish special accessor for the Controller instance
sidenavCtrl.$toggleOpen = toggleOpen;
Expand Down Expand Up @@ -440,6 +441,101 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $mdInterac
}
}

function enableDragging() {
$mdGesture.register(element, 'drag', { horizontal: true });

element
.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);

var style = getComputedStyle(element[0]);
var sidenavWidth = parseInt(style.width);
var isRightSidenav = element.hasClass('md-sidenav-right');
var accelerationBound = 6;

var dragCancelled = false;
var dragPercentage;
var lastOpenState;
var lastDistance = 0;
var isQuickDrag = false;

function onDragStart() {
if (element.hasClass('md-locked-open')) {
dragCancelled = true;
} else {
lastOpenState = scope.isOpen;
element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
}
}

function onDrag(ev) {
if (dragCancelled) return;

if (!isQuickDrag) {
var distance = lastDistance - ev.pointer.distanceX;
// When the current partial drag distance is bigger than the acceleration bound, then we can
// identify it as a quick drag.
isQuickDrag = isRightSidenav ? distance <= -accelerationBound : distance >= accelerationBound;
} else if (isRightSidenav && lastDistance > ev.pointer.distanceX ||
!isRightSidenav && lastDistance < ev.pointer.distanceX) {
// When the users drags the sidenav backward, then we can reset the quick drag state.
isQuickDrag = false;
}

dragPercentage = Math.round((ev.pointer.distanceX / sidenavWidth) * 100);
if (!isRightSidenav) dragPercentage = 0 - dragPercentage;

if (dragPercentage > 100) dragPercentage = 100;
else if (dragPercentage < 0) dragPercentage = 0;

element.css($mdConstant.CSS.TRANSFORM, 'translate3d(-' + (isRightSidenav ? 100 - dragPercentage : dragPercentage) + '%,0,0)');
lastDistance = ev.pointer.distanceX;
}

function onDragEnd() {
if (dragCancelled) {
dragCancelled = false;
return;
}

var remainingPercentage = 100 - dragPercentage;
var animationTime = 4 * remainingPercentage;
var shouldClose = dragPercentage > 50 || isQuickDrag;

// This validates the correct translate value. The invert is here required, because a right
// aligned sidenav will transition in the other direction.
var endTranslate = shouldClose ? isRightSidenav ? 0 : -100 : isRightSidenav ? -100 : 0;

element.css($mdConstant.CSS.TRANSITION_DURATION, animationTime + "ms");
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + endTranslate + '%,0,0)');

// Reset drag
lastDistance = 0;
isQuickDrag = false;

// Invert shouldClose here, because we need to know if the sidenav should open.
$timeout(onAnimationDone, animationTime, true, !shouldClose);
}

function onAnimationDone(isOpen) {
scope.isOpen = isOpen;
element.css($mdConstant.CSS.TRANSFORM, '');
element.css($mdConstant.CSS.TRANSITION_DURATION, '');

if (isOpen) {
if (!lastOpenState && backdrop) {
$animate.enter(backdrop, element.parent());
}

element.removeClass('_md-closed');
} else {
if (backdrop) $animate.leave(backdrop);
element.addClass('_md-closed');
}
}
}

/**
* Toggle the sideNav view and publish a promise to be resolved when
* the view animation finishes.
Expand Down
3 changes: 3 additions & 0 deletions src/components/sidenav/sidenav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ md-sidenav {
overflow: auto;
-webkit-overflow-scrolling: touch;

// This allows the most browsers to setup appropriate optimizations before the animation runs.
will-change: transform;

ul {
list-style: none;
}
Expand Down

0 comments on commit 863fb62

Please sign in to comment.