ember-cli-sliding-menu
Version:
The convenient sliding-menu component
206 lines (181 loc) • 6.61 kB
JavaScript
import Ember from 'ember';
import Movement from '../utils/movement';
/**
* Sliding menu
*/
export default Ember.Component.extend({
/**
* Default options
*/
classNameBindings: ['slidingElement'],
//Custom sliding menu class
slidingElement: 'sliding-menu',
//Movement instance
movement: null,
//Hammer instance
hammer: null,
//jQuery instance of sliding element
$slidingComponent: '',
//Default application identifier
appIdentifier: '.ember-application',
//Menu Progress manager
menuProgressService: null,
menuProgress: Ember.computed.alias('menuProgressService.menuProgress'),
//initial offset
offset: 0,
//Slide direction option
slideDirection: 'toLeft',
//Tappable/Pannable zone where animation will invoke
pannableWidth: 40,
//Default animation speed
defaultSpeed: 0.03,
initElement: function() {
this.width = this.element.offsetWidth;
var appElement = document.querySelector(this.get('appIdentifier')),
hammer = new Hammer(appElement),
slidingElement = this.get('slidingElement'),
direction = this.get('slideDirection'),
initialWidth = direction === 'toLeft' ? this.width : -Math.abs(this.width);
this.setProperties({
hammer: hammer,
screenWidth: appElement.offsetWidth,
$slidingComponent: Ember.$('.' + (slidingElement === '' ? this.get('observableElement') : slidingElement))
});
this.get('$slidingComponent').css({ transform: 'translateX(' + initialWidth + 'px)' });
this.get('hammer').on('panstart', this.handlePanStart.bind(this));
}.on('didInsertElement'),
deInitElement: function() {
this.get('hammer').destroy();
this.menuProgressService.updateProgress(0);
}.on('willDestroyElement'),
/**
* Pan start Handler
* @param event
*/
handlePanStart: function(event) {
event.preventDefault();
var movement = new Movement(event), newOffset = 0, progress = this.get('menuProgress');
this.movement = movement;
if (this.get('slideDirection') === 'toLeft') {
if (progress === -1 || movement.initX >= this.get('screenWidth') - this.get('pannableWidth')) {
newOffset = progress === 0 ? Math.abs(this.width - movement.initX) : movement.initX;
this.offset = Math.max(0, newOffset);
this.attachHandlers();
}
} else if (this.get('slideDirection') === 'toRight') {
if (progress === 1 || movement.initX <= this.get('pannableWidth')) {
newOffset = progress * this.width - movement.initX;
this.offset = Math.max(0, newOffset);
this.attachHandlers();
}
}
return false;
},
/**
* Pan move Handler
* @param event
*/
handlePanMove: function(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: function(event) {
event.preventDefault();
this.get('hammer').off('panmove', this.handlePanMove);
this.get('hammer').off('panend', this.handlePanEnd);
this.completeExpansion();
return false;
},
attachHandlers: function() {
this.get('$slidingComponent').css({ visibility: 'visible' });
this.get('hammer').on('panmove', this.handlePanMove.bind(this));
this.get('hammer').on('panend', this.handlePanEnd.bind(this));
},
/**
* Rendering observer with constraints
*/
animateSliding: function() {
var progress = this.get('menuProgress'),
translatedProgress = this.get('slideDirection') === 'toLeft' ? progress + 1 : progress - 1;
if (this.element && this.width) {
var translate = translatedProgress * this.width;
this.get('$slidingComponent').css({ transform: 'translateX(' + translate + 'px)' });
//TODO: for future possible frost glass effect
// if (additionalElement) {
// additionalElement.css({ transform: 'translateX(' + (-translate - 40) + 'px)' });
// }
}
}.observes('menuProgress'),
updateElementProgress: function() {
var 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) { this.menuProgressService.updateProgress(newProgress); }
} else {
newProgress = Math.min((this.movement.lastX + this.offset) / this.width, 1);
if (newProgress <= 1) { this.menuProgressService.updateProgress(newProgress); }
}
this.tick = false;
},
/**
* Complete exapansion of sliding element
*/
completeExpansion: function(){
var progress = this.get('menuProgress'), speed = this.movement.speedX, newProgress = 0,
inverseConstraint = 0, reverseConstraint = 0,
movementConstraint = false;
if (progress === -1 || progress === 0 || progress === 1) {
return;
}
if (this.get('slideDirection') === 'toLeft') {
inverseConstraint = -1;
reverseConstraint = 0;
movementConstraint = speed > 0.5 || speed > 0 && speed < 0.5 && progress < -0.5;
} else {
inverseConstraint = 0;
reverseConstraint = 1;
movementConstraint = speed > -0.5 || speed >= 0.5 && progress > 0.5;
}
if (movementConstraint) {
newProgress = Math.max(this.get('menuProgress') - this.get('defaultSpeed'), inverseConstraint);
this.set('menuProgress', newProgress);
} else {
newProgress = Math.min(this.get('menuProgress') + this.get('defaultSpeed'), reverseConstraint);
this.set('menuProgress', newProgress);
}
if (newProgress > inverseConstraint && newProgress < reverseConstraint) {
requestAnimationFrame(this.completeExpansion.bind(this));
}
if (newProgress === 0) { this.get('$slidingComponent').css({ visibility: 'hidden' }); }
}
// TODO: for future possible frost glass effect
// $blurredContent: null,
// initElement: function() {
// this._super();
//
// var element = this, blurredScroll = element.get('$slidingComponent').find('.blurred-scroll');
// Ember.run.later(function() {
// var contentClone = Ember.$('.screen-content').clone();
// blurredScroll.append(contentClone);
// contentClone.css({ transform: 'translateX(-40px)' });
// element.set('$blurredContent', contentClone);
// }, 1000);
//
// }.on('didInsertElement'),
//
// /**
// * Rendering observer with constraints
// */
// animateSliding: function() {
// this._super(this.get('$blurredContent'));
// }.observes('menuProgress')
});