ember-sliding-menu
Version:
The convenient sliding-menu component
214 lines (185 loc) • 6.21 kB
JavaScript
import Ember from 'ember';
import Movement from '../utils/movement';
const {
Component,
inject,
computed: { alias },
get,
set,
$,
observer
} = Ember;
/**
* Sliding menu
*/
export default Component.extend({
//Menu Progress manager
slidingMenuService: inject.service(),
menuProgress: alias('slidingMenuService.menuProgress'),
/**
* Default options
*/
// classNameBindings: ['slidingMenuClass'],
//Custom sliding menu class
slidingMenuClass: 'sliding-menu',
//Background overlay class
backgroundOverlayClass: 'background-overlay',
//Movement instance
movement: null,
//Hammer instance
hammer: null,
//jQuery instance of sliding element
$slidingComponent: '',
//Default application identifier
appIdentifier: '.ember-application',
//Main content identifier
// bodyIdentifier: '.body',//TODO: delete
//menu offset
menuOffset: 0,
//initial offset
offset: 0,
//Slide direction option
slideDirection: 'toLeft',
//Tappable/Pannable zone where animation will invoke
pannableWidth: 40,
//Default animation speed
defaultSpeed: 0.03,
didInsertElement() {
const slidingMenuService = get(this, 'slidingMenuService');
const appIdentifier = get(this, 'appIdentifier');
const slidingMenuClass = `.${get(this, 'slidingMenuClass')}`;
const width = get(this, 'element.offsetWidth');
const menuOffset = get(this, 'menuOffset');
const appElement = document.querySelector(appIdentifier);
const hammerElement = document.querySelector(slidingMenuClass);
const backgroundOverlayClass = get(this, 'backgroundOverlayClass');
const initialWidth = get(this, 'slideDirection') === 'toLeft' ? width : -Math.abs(width);
const hammer = new Hammer(hammerElement);
set(this, 'width', width);
const $slidingComponent = $(slidingMenuClass);
const $backgroundOverlayComponent = $('.' + get(this, 'backgroundOverlayClass'));
this.setProperties({
hammer,
screenWidth: get(appElement, 'offsetWidth'),
$slidingComponent: $slidingComponent
});
//init service
slidingMenuService.setProperties({
menuOffset,
width,
slidingComponent: $slidingComponent,
backgroundOverlayComponent: $backgroundOverlayComponent,
slideDirection: get(this, 'slideDirection'),
elementExist: !!get(this, 'element'),
});
get(this, '$slidingComponent').css({ transform: 'translateX(' + initialWidth + 'px)' });
get(this, 'hammer').on('panstart', this.handlePanStart.bind(this));
},
willDestroyElement() {
get(this, 'hammer').destroy();
set(this, 'menuProgress', 0);
},
/**
* Pan start Handler
* @param event
*/
handlePanStart(event) {
event.preventDefault();
const movement = new Movement(event);
const progress = get(this, 'menuProgress');
const slideDirection = get(this, 'slideDirection');
const width = get(this, 'width');
const pannableWidth = get(this, 'pannableWidth');
let newOffset = 0;
set(this, 'movement', movement);
if (slideDirection === 'toLeft') {
const screenWidth = get(this, 'screenWidth');
if (progress === -1 || movement.initX >= screenWidth - pannableWidth) {
newOffset = progress === 0 ? Math.abs(width - movement.initX) : movement.initX;
set(this, 'offset', Math.max(0, newOffset));//TODO: dry
this.attachHandlers();
}
} else if (slideDirection === 'toRight') {
if (progress === 1 || movement.initX <= pannableWidth) {
newOffset = progress * this.width - movement.initX;
set(this, 'offset', Math.max(0, newOffset));//TODO: dry
this.attachHandlers();
}
}
return false;
},
/**
* Pan move Handler
* @param event
*/
handlePanMove(event) {
event.preventDefault();
this.movement.push(event);
if (!this.tick) {
this.tick = true;
requestAnimationFrame(this.updateElementProgress.bind(this));
}
return false;
},
/**
* Pan end Handler
* @param event
*/
handlePanEnd(event) {
event.preventDefault();
this.get('hammer').off('panmove', this.handlePanMove);
this.get('hammer').off('panend', this.handlePanEnd);
this.completeExpansion();
return false;
},
attachHandlers() {
// this.get('$slidingComponent').css({ visibility: 'visible' });
this.get('hammer').on('panmove', this.handlePanMove.bind(this));
this.get('hammer').on('panend', this.handlePanEnd.bind(this));
},
updateElementProgress() {
let newProgress = 0;
if (this.get('slideDirection') === 'toLeft') {
newProgress = -Math.abs(Math.max((this.width - this.movement.lastX + this.offset) / this.width, -1));
if (newProgress >= -1) { set(this, 'menuProgress', newProgress); }
} else {
newProgress = Math.min((this.movement.lastX + this.offset) / this.width, 1);
if (newProgress <= 1) { set(this, 'menuProgress', newProgress); }
}
this.tick = false;
},
/**
* Complete exapansion of sliding element
*/
completeExpansion() {
const progress = this.get('menuProgress');
const speed = this.movement.speedX;
const defaultSpeed = get(this, 'defaultSpeed');
let closeConstraint = 0,
openConstraint = 0,
movementConstraint = false,
newProgress = 0;
if (progress === -1 || progress === 0 || progress === 1) {
return;
}
if (this.get('slideDirection') === 'toLeft') {
closeConstraint = 0;
openConstraint = -1;
movementConstraint = speed > 0.5 || speed > 0 && speed < 0.5 && progress < -0.5;
} else {
closeConstraint = 0;
openConstraint = 1;
movementConstraint = speed <= -0.5 || progress < 0.5;
}
if (movementConstraint) {
newProgress = Math.max(progress - defaultSpeed, closeConstraint);
} else {
newProgress = Math.min(progress + defaultSpeed, openConstraint);
}
set(this, 'menuProgress', newProgress);
if (newProgress > closeConstraint && newProgress < openConstraint) {
requestAnimationFrame(this.completeExpansion.bind(this));
}
// if (newProgress === 0) { this.get('$slidingComponent').css({ visibility: 'hidden' }); }
}
});