refactor(animations): inline css animations

This commit is contained in:
Adam Bradley
2016-02-09 16:22:40 -06:00
parent de76cefcf3
commit da18868636
94 changed files with 1071 additions and 6905 deletions

View File

@@ -3,12 +3,16 @@ import {SlideEdgeGesture} from '../../gestures/slide-edge-gesture';
import {assign} from '../../util/util';
/**
* Gesture attached to the content which the menu is assigned to
*/
export class MenuContentGesture extends SlideEdgeGesture {
constructor(public menu: Menu, targetEl: Element, options = {}) {
constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) {
super(targetEl, assign({
direction: (menu.side === 'left' || menu.side === 'right') ? 'x' : 'y',
super(contentEle, assign({
direction: 'x',
edge: menu.side,
threshold: 0,
maxEdgeStart: menu.maxEdgeStart || 75
@@ -20,21 +24,33 @@ export class MenuContentGesture extends SlideEdgeGesture {
canStart(ev) {
let menu = this.menu;
console.debug('menu canStart,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
if (!menu.isEnabled || !menu.isSwipeEnabled) {
console.debug('menu canStart, isEnabled', menu.isEnabled, 'isSwipeEnabled', menu.isSwipeEnabled, 'side', menu.side);
console.debug('menu can not start, isEnabled:', menu.isEnabled, 'isSwipeEnabled:', menu.isSwipeEnabled, 'side:', menu.side);
return false;
}
if (ev.distance > 50) {
// the distance is longer than you'd expect a side menu swipe to be
console.debug('menu canStart, distance too far', ev.distance, 'side', menu.side);
console.debug('menu can not start, distance too far:', ev.distance, 'side:', menu.side);
return false;
}
console.debug('menu canStart,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
if (menu.side === 'left') {
if (menu.side === 'right') {
// right side
if (menu.isOpen) {
// right side, opened
return true;
} else {
// right side, closed
if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) {
return super.canStart(ev);
}
}
} else {
// left side
if (menu.isOpen) {
// left side, opened
@@ -47,18 +63,6 @@ export class MenuContentGesture extends SlideEdgeGesture {
}
}
} else if (menu.side === 'right') {
// right side
if (menu.isOpen) {
// right side, opened
return true;
} else {
// right side, closed
if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) {
return super.canStart(ev);
}
}
}
// didn't pass the test, don't open this menu
@@ -67,23 +71,51 @@ export class MenuContentGesture extends SlideEdgeGesture {
// Set CSS, then wait one frame for it to apply before sliding starts
onSlideBeforeStart(slide, ev) {
console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
this.menu.setProgressStart();
}
onSlide(slide, ev) {
this.menu.setProgess(slide.distance / slide.max);
let z = (this.menu.side === 'right' ? slide.min : slide.max);
let stepValue = (slide.distance / z);
console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue);
this.menu.setProgessStep(stepValue);
}
onSlideEnd(slide, ev) {
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
this.menu.setProgressEnd(shouldComplete);
let z = (this.menu.side === 'right' ? slide.min : slide.max);
let shouldComplete = (Math.abs(ev.velocityX) > 0.2) ||
(Math.abs(slide.delta) > Math.abs(z) * 0.5);
let currentStepValue = (slide.distance / z);
console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'delta', slide.delta, 'velocityX', ev.velocityX, 'min', slide.min, 'max', slide.max, 'shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
this.menu.setProgressEnd(shouldComplete, currentStepValue);
}
getElementStartPos(slide, ev) {
if (this.menu.side === 'right') {
// right menu
return this.menu.isOpen ? slide.min : slide.max;
}
// left menu
return this.menu.isOpen ? slide.max : slide.min;
}
getSlideBoundaries() {
getSlideBoundaries(): {min: number, max: number} {
if (this.menu.side === 'right') {
// right menu
return {
min: -this.menu.width(),
max: 0
};
}
// left menu
return {
min: 0,
max: this.menu.width()
@@ -93,40 +125,12 @@ export class MenuContentGesture extends SlideEdgeGesture {
/**
* Support dragging the target menu as well as the content.
* Gesture attached to the actual menu itself
*/
export class TargetGesture extends MenuContentGesture {
constructor(menu: Menu) {
super(menu, menu.getNativeElement(), {
export class MenuTargetGesture extends MenuContentGesture {
constructor(menu: Menu, menuEle: HTMLElement) {
super(menu, menuEle, {
maxEdgeStart: 0
});
}
}
export class LeftMenuGesture extends MenuContentGesture {
constructor(menu: Menu) {
super(menu, menu.getContentElement());
}
}
export class RightMenuGesture extends MenuContentGesture {
constructor(menu: Menu) {
super(menu, menu.getContentElement());
}
onSlide(slide, ev) {
this.menu.setProgess(slide.distance / slide.min);
}
getElementStartPos(slide, ev) {
return this.menu.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.menu.width(),
max: 0
};
}
}

View File

@@ -10,66 +10,46 @@ import {Animation} from '../../animations/animation';
* @private
*/
export class MenuType {
open: Animation = new Animation();
close: Animation = new Animation();
ani: Animation = new Animation();
isOpening: boolean;
seek: Animation;
setOpen(shouldOpen) {
return new Promise(resolve => {
if (shouldOpen) {
this.open.playbackRate(1).onFinish(resolve, true).play();
} else {
this.close.playbackRate(1).onFinish(resolve, true).play();
}
});
setOpen(shouldOpen: boolean, done: Function) {
this.ani
.onFinish(done, true)
.reverse(!shouldOpen)
.play();
}
setProgressStart(isOpen) {
setProgressStart(isOpen: boolean) {
this.isOpening = !isOpen;
this.seek && this.seek.dispose();
// clone the correct animation depending on open/close
if (this.isOpening) {
this.seek = this.open.clone();
} else {
this.seek = this.close.clone();
}
// the cloned animation should not use an easing curve during seek
this.seek.easing('linear').progressStart();
this.ani
.reverse(isOpen)
.progressStart();
}
setProgess(value) {
setProgessStep(stepValue: number) {
// adjust progress value depending if it opening or closing
if (!this.isOpening) {
value = 1 - value;
}
this.seek.progress(value);
this.ani.progressStep(stepValue);
}
setProgressEnd(shouldComplete) {
let resolve;
let promise = new Promise(res => { resolve = res });
setProgressEnd(shouldComplete: boolean, currentStepValue: number, done: Function) {
let isOpen = (this.isOpening && shouldComplete);
if (!this.isOpening && !shouldComplete) {
isOpen = true;
}
this.seek.progressEnd(shouldComplete).then(() => {
this.ani.onFinish(() => {
this.isOpening = false;
resolve(isOpen);
});
done(isOpen);
}, true);
return promise;
this.ani.progressEnd(shouldComplete, currentStepValue);
}
ngOnDestroy() {
this.open && this.open.dispose();
this.close && this.close.dispose();
this.seek && this.seek.dispose();
destroy() {
this.ani && this.ani.destroy();
}
}
@@ -84,26 +64,20 @@ class MenuRevealType extends MenuType {
constructor(menu) {
super();
let easing = 'ease';
let duration = 250;
let openedX = (menu.width() * (menu.side == 'right' ? -1 : 1)) + 'px';
let closedX = '0px'
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
this.ani
.easing('ease')
.duration(250);
let contentOpen = new Animation(menu.getContentElement());
contentOpen.fromTo(TRANSLATE_X, closedX, openedX);
this.open.add(contentOpen);
let contentClose = new Animation(menu.getContentElement());
contentClose.fromTo(TRANSLATE_X, openedX, closedX);
this.close.add(contentClose);
contentOpen.fromTo('translateX', '0px', openedX);
this.ani.add(contentOpen);
}
}
MenuController.registerType('reveal', MenuRevealType);
/**
* Menu Push Type
* The content slides over to reveal the menu underneath.
@@ -113,8 +87,9 @@ class MenuPushType extends MenuType {
constructor(menu) {
super();
let easing = 'ease';
let duration = 250;
this.ani
.easing('ease')
.duration(250);
let contentOpenedX, menuClosedX, menuOpenedX;
@@ -127,30 +102,19 @@ class MenuPushType extends MenuType {
menuOpenedX = '0px';
menuClosedX = -menu.width() + 'px';
}
// left side
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
let menuAni = new Animation(menu.getMenuElement());
menuAni.fromTo('translateX', menuClosedX, menuOpenedX);
this.ani.add(menuAni);
let menuOpen = new Animation(menu.getMenuElement());
menuOpen.fromTo(TRANSLATE_X, menuClosedX, menuOpenedX);
this.open.add(menuOpen);
let contentOpen = new Animation(menu.getContentElement());
contentOpen.fromTo(TRANSLATE_X, '0px', contentOpenedX);
this.open.add(contentOpen);
let menuClose = new Animation(menu.getMenuElement());
menuClose.fromTo(TRANSLATE_X, menuOpenedX, menuClosedX);
this.close.add(menuClose);
let contentClose = new Animation(menu.getContentElement());
contentClose.fromTo(TRANSLATE_X, contentOpenedX, '0px');
this.close.add(contentClose);
let contentApi = new Animation(menu.getContentElement());
contentApi.fromTo('translateX', '0px', contentOpenedX);
this.ani.add(contentApi);
}
}
MenuController.registerType('push', MenuPushType);
/**
* Menu Overlay Type
* The menu slides over the content. The content
@@ -160,9 +124,9 @@ class MenuOverlayType extends MenuType {
constructor(menu) {
super();
let easing = 'ease';
let duration = 250;
let backdropOpacity = 0.35;
this.ani
.easing('ease')
.duration(250);
let closedX, openedX;
if (menu.side == 'right') {
@@ -176,28 +140,13 @@ class MenuOverlayType extends MenuType {
openedX = '8px';
}
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
let menuAni = new Animation(menu.getMenuElement());
menuAni.fromTo('translateX', closedX, openedX);
this.ani.add(menuAni);
let menuOpen = new Animation(menu.getMenuElement());
menuOpen.fromTo(TRANSLATE_X, closedX, openedX);
this.open.add(menuOpen);
let backdropOpen = new Animation(menu.getBackdropElement());
backdropOpen.fromTo(OPACITY, 0.01, backdropOpacity);
this.open.add(backdropOpen);
let menuClose = new Animation(menu.getMenuElement());
menuClose.fromTo(TRANSLATE_X, openedX, closedX);
this.close.add(menuClose);
let backdropClose = new Animation(menu.getBackdropElement());
backdropClose.fromTo(OPACITY, backdropOpacity, 0.01);
this.close.add(backdropClose);
let backdropApi = new Animation(menu.getBackdropElement());
backdropApi.fromTo('opacity', 0.01, 0.35);
this.ani.add(backdropApi);
}
}
MenuController.registerType('overlay', MenuOverlayType);
const OPACITY = 'opacity';
const TRANSLATE_X = 'translateX';

View File

@@ -4,10 +4,11 @@ import {Ion} from '../ion';
import {Config} from '../../config/config';
import {Platform} from '../../platform/platform';
import {Keyboard} from '../../util/keyboard';
import * as gestures from './menu-gestures';
import {MenuContentGesture, MenuTargetGesture} from './menu-gestures';
import {Gesture} from '../../gestures/gesture';
import {MenuController} from './menu-controller';
import {MenuType} from './menu-types';
import {isFalseProperty} from '../../util/util';
/**
@@ -27,9 +28,10 @@ import {MenuType} from './menu-types';
export class Menu extends Ion {
private _preventTime: number = 0;
private _cntEle: HTMLElement;
private _gesture: Gesture;
private _targetGesture: Gesture;
private _cntGesture: Gesture;
private _menuGesture: Gesture;
private _type: MenuType;
private _resizeUnreg: Function;
/**
@@ -113,76 +115,57 @@ export class Menu extends Ion {
let content = self.content;
self._cntEle = (content instanceof Node) ? content : content && content.getNativeElement && content.getNativeElement();
// requires content element
if (!self._cntEle) {
return console.error('Menu: must have a [content] element to listen for drag events on. Example:\n\n<ion-menu [content]="content"></ion-menu>\n\n<ion-nav #content></ion-nav>');
}
// normalize the "side"
if (self.side !== 'left' && self.side !== 'right') {
self.side = 'left';
}
self._renderer.setElementAttribute(self._elementRef.nativeElement, 'side', self.side);
if (self.swipeEnabled === 'false') {
// normalize the "type"
if (!self.type) {
self.type = self._config.get('menuType');
}
self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type);
// add the gesture listeners
self._zone.runOutsideAngular(function() {
self._cntGesture = new MenuContentGesture(self, self.getContentElement());
self._menuGesture = new MenuTargetGesture(self, self.getNativeElement());
self.onContentClick = function(ev: UIEvent) {
if (self.isEnabled) {
ev.preventDefault();
ev.stopPropagation();
self.close();
}
};
});
if (isFalseProperty(self.swipeEnabled)) {
self.isSwipeEnabled = false;
}
this._menuCtrl.register(self);
self._initGesture();
self._initType(self.type);
self._cntEle.classList.add('menu-content');
self._cntEle.classList.add('menu-content-' + self.type);
self.onContentClick = function(ev: UIEvent) {
if (self.isEnabled) {
ev.preventDefault();
ev.stopPropagation();
self.close();
}
};
// register this menu with the app's menu controller
self._menuCtrl.register(self);
}
/**
* @private
*/
private _initGesture() {
this._zone.runOutsideAngular(() => {
switch(this.side) {
case 'right':
this._gesture = new gestures.RightMenuGesture(this);
break;
case 'left':
this._gesture = new gestures.LeftMenuGesture(this);
break;
}
this._targetGesture = new gestures.TargetGesture(this);
});
}
/**
* @private
*/
private _initType(type) {
type = type && type.trim().toLowerCase();
if (!type) {
type = this._config.get('menuType');
}
this.type = type;
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'menuType', type);
}
/**
* @private
*/
private _getType() {
private _getType(): MenuType {
if (!this._type) {
this._type = MenuController.create(this.type, this);
if (this._config.get('animate') === false) {
this._type.open.duration(33);
this._type.close.duration(33);
this._type.ani.duration(0);
}
}
return this._type;
@@ -193,17 +176,20 @@ export class Menu extends Ion {
* @param {boolean} shouldOpen If the Menu is open or not.
* @return {Promise} returns a promise once set
*/
setOpen(shouldOpen) {
setOpen(shouldOpen): Promise<boolean> {
// _isPrevented is used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the menuToggle button
if ((shouldOpen && this.isOpen) || this._isPrevented()) {
return Promise.resolve();
return Promise.resolve(this.isOpen);
}
this._before();
return this._getType().setOpen(shouldOpen).then(() => {
this._after(shouldOpen);
return new Promise(resolve => {
this._getType().setOpen(shouldOpen, () => {
this._after(shouldOpen);
resolve(this.isOpen);
});
});
}
@@ -215,30 +201,30 @@ export class Menu extends Ion {
if (this._isPrevented() || !this.isEnabled || !this.isSwipeEnabled) return;
this._before();
this._getType().setProgressStart(this.isOpen);
}
/**
* @private
*/
setProgess(value) {
setProgessStep(stepValue: number) {
// user actively dragging the menu
if (this.isEnabled && this.isSwipeEnabled) {
this._prevent();
this._getType().setProgess(value);
this.opening.next(value);
this._getType().setProgessStep(stepValue);
this.opening.next(stepValue);
}
}
/**
* @private
*/
setProgressEnd(shouldComplete) {
setProgressEnd(shouldComplete: boolean, currentStepValue: number) {
// user has finished dragging the menu
if (this.isEnabled && this.isSwipeEnabled) {
this._prevent();
this._getType().setProgressEnd(shouldComplete).then(isOpen => {
this._getType().setProgressEnd(shouldComplete, currentStepValue, (isOpen) => {
console.debug('menu, progress end', this.side);
this._after(isOpen);
});
}
@@ -262,7 +248,7 @@ export class Menu extends Ion {
/**
* @private
*/
private _after(isOpen) {
private _after(isOpen: boolean) {
// keep opening/closing the menu disabled for a touch more yet
// only add listeners/css if it's enabled and isOpen
// and only remove listeners/css if it's not open
@@ -302,24 +288,24 @@ export class Menu extends Ion {
}
/**
* Progamatically open the Menu
* @return {Promise} returns a promise when the menu is fully opened
* Progamatically open the Menu.
* @return {Promise} returns a promise when the menu is fully opened.
*/
open() {
return this.setOpen(true);
}
/**
* Progamatically close the Menu
* @return {Promise} returns a promise when the menu is fully closed
* Progamatically close the Menu.
* @return {Promise} returns a promise when the menu is fully closed.
*/
close() {
return this.setOpen(false);
}
/**
* Toggle the menu. If it's closed, it will open, and if opened, it will close
* @return {Promise} returns a promise when the menu has been toggled
* Toggle the menu. If it's closed, it will open, and if opened, it will close.
* @return {Promise} returns a promise when the menu has been toggled.
*/
toggle() {
return this.setOpen(!this.isOpen);
@@ -331,7 +317,7 @@ export class Menu extends Ion {
* @param {boolean} shouldEnable True if it should be enabled, false if not.
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
*/
enable(shouldEnable: boolean) {
enable(shouldEnable: boolean): Menu {
this.isEnabled = shouldEnable;
if (!shouldEnable && this.isOpen) {
this.close();
@@ -344,7 +330,7 @@ export class Menu extends Ion {
* @param {boolean} shouldEnable True if it should be swipe-able, false if not.
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
*/
swipeEnable(shouldEnable: boolean) {
swipeEnable(shouldEnable: boolean): Menu {
this.isSwipeEnabled = shouldEnable;
return this;
}
@@ -352,21 +338,21 @@ export class Menu extends Ion {
/**
* @private
*/
getMenuElement() {
getMenuElement(): HTMLElement {
return this.getNativeElement();
}
/**
* @private
*/
getContentElement() {
getContentElement(): HTMLElement {
return this._cntEle;
}
/**
* @private
*/
getBackdropElement() {
getBackdropElement(): HTMLElement {
return this.backdrop.elementRef.nativeElement;
}
@@ -375,9 +361,10 @@ export class Menu extends Ion {
*/
ngOnDestroy() {
this._menuCtrl.unregister(this);
this._gesture && this._gesture.destroy();
this._targetGesture && this._targetGesture.destroy();
this._type && this._type.ngOnDestroy();
this._cntGesture && this._cntGesture.destroy();
this._menuGesture && this._menuGesture.destroy();
this._type && this._type.destroy();
this._resizeUnreg && this._resizeUnreg();
this._cntEle = null;
}

View File

@@ -17,5 +17,24 @@
</ion-menu>
<ion-menu [content]="content" id="myMenuId2" type="overlay" side="right">
<ion-toolbar danger>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item menuToggle="myMenuId2" detail-none>
Close Right Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav id="nav" [root]="rootView" #content swipe-back-enabled="false"></ion-nav>

View File

@@ -9,15 +9,21 @@
Overlay Menu
</ion-title>
<button menuToggle="myMenuId2" end>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
<ion-content #content padding>
<h3>Content</h3>
<p>
<button menuToggle="myMenuId">Toggle Left Overlay Menu</button>
</p>
<p>
<button menuToggle="myMenuId">Toggle Left Menu</button>
<button menuToggle="myMenuId2">Toggle Right Overlay Menu</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>

View File

@@ -17,5 +17,24 @@
</ion-menu>
<ion-menu [content]="content" type="push" side="right">
<ion-toolbar danger>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item menuToggle="right" detail-none>
Close Right Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav id="nav" [root]="rootView" #content swipe-back-enabled="false"></ion-nav>

View File

@@ -9,15 +9,21 @@
Push Menu
</ion-title>
<button menuToggle="right" end>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
<ion-content #content padding>
<h3>Content</h3>
<p>
<button menuToggle="left">Toggle Left Push Menu</button>
</p>
<p>
<button menuToggle="left">Toggle Left Menu</button>
<button menuToggle="right">Toggle Right Push Menu</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>

View File

@@ -17,5 +17,23 @@
</ion-menu>
<ion-menu [content]="content" type="reveal" side="right">
<ion-toolbar danger>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item menuToggle="right" detail-none>
Close Right Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav id="nav" [root]="rootView" #content swipe-back-enabled="false"></ion-nav>

View File

@@ -9,15 +9,21 @@
Reveal Menu
</ion-title>
<button menuToggle="right" end>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
<ion-content #content padding>
<h3>Content</h3>
<p>
<button menuToggle>Toggle Left Reveal Menu</button>
</p>
<p>
<button menuToggle>Toggle Left Menu</button>
<button menuToggle="right">Toggle Right Reveal Menu</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>