-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat(sidenav): add gestures to sidenav to allow dragging the sidenav #6174
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> |
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(); | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -197,6 +197,7 @@ function SidenavFocusDirective() { | |||
* @param {expression=} md-is-open A model bound to whether the sidenav is opened. | ||||
* @param {boolean=} md-disable-backdrop When present in the markup, the sidenav will not show a backdrop. | ||||
* @param {string=} md-component-id componentId to use with $mdSidenav service. | ||||
* @param {boolean=} md-disable-drag Disables the abbility to drag the sidenav | ||||
* @param {expression=} md-is-locked-open When this expression evalutes to true, | ||||
* the sidenav 'locks open': it falls into the content's flow instead | ||||
* of appearing over it. This overrides the `md-is-open` attribute. | ||||
|
@@ -209,7 +210,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, $animate, $compile, $parse, $log, $q, $document) { | ||||
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $mdGesture, $parse, $log, $q, $document, $timeout) { | ||||
return { | ||||
restrict: 'E', | ||||
scope: { | ||||
|
@@ -265,6 +266,8 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, | |||
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; | ||||
|
@@ -335,6 +338,101 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, | |||
} | ||||
} | ||||
|
||||
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; | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd prefer if (...) {
if (...) {
}
else {
...
}
}
else {
...
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that will look more clear. |
||||
|
||||
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; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's because of my percentage calculation
So every percentage needs to run for 4ms to reach a smooth transition as always. |
||||
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. | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
element[0].offsetWidth
won't work better?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That won't work, because the sidenav is hidden, that's why
offsetWidth
doesn't work