You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

565 lines
20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* off-canvas
* @param {type} $
* @param {type} window
* @param {type} document
* @param {type} action
* @returns {undefined}
*/
(function($, window, document, name) {
var CLASS_OFF_CANVAS_LEFT = $.className('off-canvas-left');
var CLASS_OFF_CANVAS_RIGHT = $.className('off-canvas-right');
var CLASS_ACTION_BACKDROP = $.className('off-canvas-backdrop');
var CLASS_OFF_CANVAS_WRAP = $.className('off-canvas-wrap');
var CLASS_SLIDE_IN = $.className('slide-in');
var CLASS_ACTIVE = $.className('active');
var CLASS_TRANSITIONING = $.className('transitioning');
var SELECTOR_INNER_WRAP = $.classSelector('.inner-wrap');
var OffCanvas = $.Class.extend({
init: function(element, options) {
this.wrapper = this.element = element;
this.scroller = this.wrapper.querySelector(SELECTOR_INNER_WRAP);
this.classList = this.wrapper.classList;
if (this.scroller) {
this.options = $.extend(true, {
dragThresholdX: 10,
scale: 0.8,
opacity: 0.1,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|VIDEO)$/
},
}, options);
document.body.classList.add($.className('fullscreen')); //fullscreen
this.refresh();
this.initEvent();
}
},
_preventDefaultException: function(el, exceptions) {
for (var i in exceptions) {
if (exceptions[i].test(el[i])) {
return true;
}
}
return false;
},
refresh: function(offCanvas) {
// offCanvas && !offCanvas.classList.contains(CLASS_ACTIVE) && this.classList.remove(CLASS_ACTIVE);
this.slideIn = this.classList.contains(CLASS_SLIDE_IN);
this.scalable = this.classList.contains($.className('scalable')) && !this.slideIn;
this.scroller = this.wrapper.querySelector(SELECTOR_INNER_WRAP);
// !offCanvas && this.scroller.classList.remove(CLASS_TRANSITIONING);
// !offCanvas && this.scroller.setAttribute('style', '');
this.offCanvasLefts = this.wrapper.querySelectorAll('.' + CLASS_OFF_CANVAS_LEFT);
this.offCanvasRights = this.wrapper.querySelectorAll('.' + CLASS_OFF_CANVAS_RIGHT);
if (offCanvas) {
if (offCanvas.classList.contains(CLASS_OFF_CANVAS_LEFT)) {
this.offCanvasLeft = offCanvas;
} else if (offCanvas.classList.contains(CLASS_OFF_CANVAS_RIGHT)) {
this.offCanvasRight = offCanvas;
}
} else {
this.offCanvasRight = this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_RIGHT);
this.offCanvasLeft = this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_LEFT);
}
this.offCanvasRightWidth = this.offCanvasLeftWidth = 0;
this.offCanvasLeftSlideIn = this.offCanvasRightSlideIn = false;
if (this.offCanvasRight) {
this.offCanvasRightWidth = this.offCanvasRight.offsetWidth;
this.offCanvasRightSlideIn = this.slideIn && (this.offCanvasRight.parentNode === this.wrapper);
// this.offCanvasRight.classList.remove(CLASS_TRANSITIONING);
// this.offCanvasRight.classList.remove(CLASS_ACTIVE);
// this.offCanvasRight.setAttribute('style', '');
}
if (this.offCanvasLeft) {
this.offCanvasLeftWidth = this.offCanvasLeft.offsetWidth;
this.offCanvasLeftSlideIn = this.slideIn && (this.offCanvasLeft.parentNode === this.wrapper);
// this.offCanvasLeft.classList.remove(CLASS_TRANSITIONING);
// this.offCanvasLeft.classList.remove(CLASS_ACTIVE);
// this.offCanvasLeft.setAttribute('style', '');
}
this.backdrop = this.scroller.querySelector('.' + CLASS_ACTION_BACKDROP);
this.options.dragThresholdX = this.options.dragThresholdX || 10;
this.visible = false;
this.startX = null;
this.lastX = null;
this.offsetX = null;
this.lastTranslateX = null;
},
handleEvent: function(e) {
switch (e.type) {
case $.EVENT_START:
e.target && !this._preventDefaultException(e.target, this.options.preventDefaultException) && e.preventDefault();
break;
case 'webkitTransitionEnd': //有个bug需要处理需要考虑假设没有触发webkitTransitionEnd的情况
if (e.target === this.scroller) {
this._dispatchEvent();
}
break;
case 'drag':
var detail = e.detail;
if (!this.startX) {
this.startX = detail.center.x;
this.lastX = this.startX;
} else {
this.lastX = detail.center.x;
}
if (!this.isDragging && Math.abs(this.lastX - this.startX) > this.options.dragThresholdX && (detail.direction === 'left' || (detail.direction === 'right'))) {
if (this.slideIn) {
this.scroller = this.wrapper.querySelector(SELECTOR_INNER_WRAP);
if (this.classList.contains(CLASS_ACTIVE)) {
if (this.offCanvasRight && this.offCanvasRight.classList.contains(CLASS_ACTIVE)) {
this.offCanvas = this.offCanvasRight;
this.offCanvasWidth = this.offCanvasRightWidth;
} else {
this.offCanvas = this.offCanvasLeft;
this.offCanvasWidth = this.offCanvasLeftWidth;
}
} else {
if (detail.direction === 'left' && this.offCanvasRight) {
this.offCanvas = this.offCanvasRight;
this.offCanvasWidth = this.offCanvasRightWidth;
} else if (detail.direction === 'right' && this.offCanvasLeft) {
this.offCanvas = this.offCanvasLeft;
this.offCanvasWidth = this.offCanvasLeftWidth;
} else {
this.scroller = null;
}
}
} else {
if (this.classList.contains(CLASS_ACTIVE)) {
if (detail.direction === 'left') {
this.offCanvas = this.offCanvasLeft;
this.offCanvasWidth = this.offCanvasLeftWidth;
} else {
this.offCanvas = this.offCanvasRight;
this.offCanvasWidth = this.offCanvasRightWidth;
}
} else {
if (detail.direction === 'right') {
this.offCanvas = this.offCanvasLeft;
this.offCanvasWidth = this.offCanvasLeftWidth;
} else {
this.offCanvas = this.offCanvasRight;
this.offCanvasWidth = this.offCanvasRightWidth;
}
}
}
if (this.offCanvas && this.scroller) {
this.startX = this.lastX;
this.isDragging = true;
$.gestures.session.lockDirection = true; //锁定方向
$.gestures.session.startDirection = detail.direction;
this.offCanvas.classList.remove(CLASS_TRANSITIONING);
this.scroller.classList.remove(CLASS_TRANSITIONING);
this.offsetX = this.getTranslateX();
this._initOffCanvasVisible();
}
}
if (this.isDragging) {
this.updateTranslate(this.offsetX + (this.lastX - this.startX));
detail.gesture.preventDefault();
e.stopPropagation();
}
break;
case 'dragend':
if (this.isDragging) {
var detail = e.detail;
var direction = detail.direction;
this.isDragging = false;
this.offCanvas.classList.add(CLASS_TRANSITIONING);
this.scroller.classList.add(CLASS_TRANSITIONING);
var ratio = 0;
var x = this.getTranslateX();
if (!this.slideIn) {
if (x >= 0) {
ratio = (this.offCanvasLeftWidth && (x / this.offCanvasLeftWidth)) || 0;
} else {
ratio = (this.offCanvasRightWidth && (x / this.offCanvasRightWidth)) || 0;
}
if (ratio === 0) {
this.openPercentage(0);
this._dispatchEvent(); //此处不触发webkitTransitionEnd,所以手动dispatch
return;
}
if (direction === 'right' && ratio >= 0 && (ratio >= 0.5 || detail.swipe)) { //右滑打开
this.openPercentage(100);
} else if (direction === 'right' && ratio < 0 && (ratio > -0.5 || detail.swipe)) { //右滑关闭
this.openPercentage(0);
} else if (direction === 'right' && ratio > 0 && ratio < 0.5) { //右滑还原关闭
this.openPercentage(0);
} else if (direction === 'right' && ratio < 0.5) { //右滑还原打开
this.openPercentage(-100);
} else if (direction === 'left' && ratio <= 0 && (ratio <= -0.5 || detail.swipe)) { //左滑打开
this.openPercentage(-100);
} else if (direction === 'left' && ratio > 0 && (ratio <= 0.5 || detail.swipe)) { //左滑关闭
this.openPercentage(0);
} else if (direction === 'left' && ratio < 0 && ratio >= -0.5) { //左滑还原关闭
this.openPercentage(0);
} else if (direction === 'left' && ratio > 0.5) { //左滑还原打开
this.openPercentage(100);
} else { //默认关闭
this.openPercentage(0);
}
if (ratio === 1 || ratio === -1) { //此处不触发webkitTransitionEnd,所以手动dispatch
this._dispatchEvent();
}
} else {
if (x >= 0) {
ratio = (this.offCanvasRightWidth && (x / this.offCanvasRightWidth)) || 0;
} else {
ratio = (this.offCanvasLeftWidth && (x / this.offCanvasLeftWidth)) || 0;
}
if (direction === 'right' && ratio <= 0 && (ratio >= -0.5 || detail.swipe)) { //右滑打开
this.openPercentage(100);
} else if (direction === 'right' && ratio > 0 && (ratio >= 0.5 || detail.swipe)) { //右滑关闭
this.openPercentage(0);
} else if (direction === 'right' && ratio <= -0.5) { //右滑还原关闭
this.openPercentage(0);
} else if (direction === 'right' && ratio > 0 && ratio <= 0.5) { //右滑还原打开
this.openPercentage(-100);
} else if (direction === 'left' && ratio >= 0 && (ratio <= 0.5 || detail.swipe)) { //左滑打开
this.openPercentage(-100);
} else if (direction === 'left' && ratio < 0 && (ratio <= -0.5 || detail.swipe)) { //左滑关闭
this.openPercentage(0);
} else if (direction === 'left' && ratio >= 0.5) { //左滑还原关闭
this.openPercentage(0);
} else if (direction === 'left' && ratio >= -0.5 && ratio < 0) { //左滑还原打开
this.openPercentage(100);
} else {
this.openPercentage(0);
}
if (ratio === 1 || ratio === -1 || ratio === 0) {
this._dispatchEvent();
return;
}
}
}
break;
}
},
_dispatchEvent: function() {
if (this.classList.contains(CLASS_ACTIVE)) {
$.trigger(this.wrapper, 'shown', this);
} else {
$.trigger(this.wrapper, 'hidden', this);
}
},
_initOffCanvasVisible: function() {
if (!this.visible) {
this.visible = true;
if (this.offCanvasLeft) {
this.offCanvasLeft.style.visibility = 'visible';
}
if (this.offCanvasRight) {
this.offCanvasRight.style.visibility = 'visible';
}
}
},
initEvent: function() {
var self = this;
if (self.backdrop) {
self.backdrop.addEventListener('tap', function(e) {
self.close();
e.detail.gesture.preventDefault();
});
}
if (this.classList.contains($.className('draggable'))) {
this.wrapper.addEventListener($.EVENT_START, this); //临时处理
this.wrapper.addEventListener('drag', this);
this.wrapper.addEventListener('dragend', this);
}
this.wrapper.addEventListener('webkitTransitionEnd', this);
},
openPercentage: function(percentage) {
var p = percentage / 100;
if (!this.slideIn) {
if (this.offCanvasLeft && percentage >= 0) {
this.updateTranslate(this.offCanvasLeftWidth * p);
this.offCanvasLeft.classList[p !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
} else if (this.offCanvasRight && percentage <= 0) {
this.updateTranslate(this.offCanvasRightWidth * p);
this.offCanvasRight.classList[p !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
}
this.classList[p !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
} else {
if (this.offCanvasLeft && percentage >= 0) {
p = p === 0 ? -1 : 0;
this.updateTranslate(this.offCanvasLeftWidth * p);
this.offCanvasLeft.classList[percentage !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
} else if (this.offCanvasRight && percentage <= 0) {
p = p === 0 ? 1 : 0;
this.updateTranslate(this.offCanvasRightWidth * p);
this.offCanvasRight.classList[percentage !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
}
this.classList[percentage !== 0 ? 'add' : 'remove'](CLASS_ACTIVE);
}
},
updateTranslate: function(x) {
if (x !== this.lastTranslateX) {
if (!this.slideIn) {
if ((!this.offCanvasLeft && x > 0) || (!this.offCanvasRight && x < 0)) {
this.setTranslateX(0);
return;
}
if (this.leftShowing && x > this.offCanvasLeftWidth) {
this.setTranslateX(this.offCanvasLeftWidth);
return;
}
if (this.rightShowing && x < -this.offCanvasRightWidth) {
this.setTranslateX(-this.offCanvasRightWidth);
return;
}
this.setTranslateX(x);
if (x >= 0) {
this.leftShowing = true;
this.rightShowing = false;
if (x > 0) {
if (this.offCanvasLeft) {
$.each(this.offCanvasLefts, function(index, offCanvas) {
if (offCanvas === this.offCanvasLeft) {
this.offCanvasLeft.style.zIndex = 0;
} else {
offCanvas.style.zIndex = -1;
}
}.bind(this));
}
if (this.offCanvasRight) {
this.offCanvasRight.style.zIndex = -1;
}
}
} else {
this.rightShowing = true;
this.leftShowing = false;
if (this.offCanvasRight) {
$.each(this.offCanvasRights, function(index, offCanvas) {
if (offCanvas === this.offCanvasRight) {
offCanvas.style.zIndex = 0;
} else {
offCanvas.style.zIndex = -1;
}
}.bind(this));
}
if (this.offCanvasLeft) {
this.offCanvasLeft.style.zIndex = -1;
}
}
} else {
if (this.offCanvas.classList.contains(CLASS_OFF_CANVAS_RIGHT)) {
if (x < 0) {
this.setTranslateX(0);
return;
}
if (x > this.offCanvasRightWidth) {
this.setTranslateX(this.offCanvasRightWidth);
return;
}
} else {
if (x > 0) {
this.setTranslateX(0);
return;
}
if (x < -this.offCanvasLeftWidth) {
this.setTranslateX(-this.offCanvasLeftWidth);
return;
}
}
this.setTranslateX(x);
}
this.lastTranslateX = x;
}
},
setTranslateX: $.animationFrame(function(x) {
if (this.scroller) {
if (this.scalable && this.offCanvas.parentNode === this.wrapper) {
var percent = Math.abs(x) / this.offCanvasWidth;
var zoomOutScale = 1 - (1 - this.options.scale) * percent;
var zoomInScale = this.options.scale + (1 - this.options.scale) * percent;
var zoomOutOpacity = 1 - (1 - this.options.opacity) * percent;
var zoomInOpacity = this.options.opacity + (1 - this.options.opacity) * percent;
if (this.offCanvas.classList.contains(CLASS_OFF_CANVAS_LEFT)) {
this.offCanvas.style.webkitTransformOrigin = '-100%';
this.scroller.style.webkitTransformOrigin = 'left';
} else {
this.offCanvas.style.webkitTransformOrigin = '200%';
this.scroller.style.webkitTransformOrigin = 'right';
}
this.offCanvas.style.opacity = zoomInOpacity;
this.offCanvas.style.webkitTransform = 'translate3d(0,0,0) scale(' + zoomInScale + ')';
this.scroller.style.webkitTransform = 'translate3d(' + x + 'px,0,0) scale(' + zoomOutScale + ')';
} else {
if (this.slideIn) {
this.offCanvas.style.webkitTransform = 'translate3d(' + x + 'px,0,0)';
} else {
this.scroller.style.webkitTransform = 'translate3d(' + x + 'px,0,0)';
}
}
}
}),
getTranslateX: function() {
if (this.offCanvas) {
var scroller = this.slideIn ? this.offCanvas : this.scroller;
var result = $.parseTranslateMatrix($.getStyles(scroller, 'webkitTransform'));
return (result && result.x) || 0;
}
return 0;
},
isShown: function(direction) {
var shown = false;
if (!this.slideIn) {
var x = this.getTranslateX();
if (direction === 'right') {
shown = this.classList.contains(CLASS_ACTIVE) && x < 0;
} else if (direction === 'left') {
shown = this.classList.contains(CLASS_ACTIVE) && x > 0;
} else {
shown = this.classList.contains(CLASS_ACTIVE) && x !== 0;
}
} else {
if (direction === 'left') {
shown = this.classList.contains(CLASS_ACTIVE) && this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_LEFT + '.' + CLASS_ACTIVE);
} else if (direction === 'right') {
shown = this.classList.contains(CLASS_ACTIVE) && this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_RIGHT + '.' + CLASS_ACTIVE);
} else {
shown = this.classList.contains(CLASS_ACTIVE) && (this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_LEFT + '.' + CLASS_ACTIVE) || this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_RIGHT + '.' + CLASS_ACTIVE));
}
}
return shown;
},
close: function() {
this._initOffCanvasVisible();
this.offCanvas = this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_RIGHT + '.' + CLASS_ACTIVE) || this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_LEFT + '.' + CLASS_ACTIVE);
this.offCanvasWidth = this.offCanvas.offsetWidth;
if (this.scroller) {
this.offCanvas.offsetHeight;
this.offCanvas.classList.add(CLASS_TRANSITIONING);
this.scroller.classList.add(CLASS_TRANSITIONING);
this.openPercentage(0);
}
},
show: function(direction) {
this._initOffCanvasVisible();
if (this.isShown(direction)) {
return false;
}
if (!direction) {
direction = this.wrapper.querySelector('.' + CLASS_OFF_CANVAS_RIGHT) ? 'right' : 'left';
}
if (direction === 'right') {
this.offCanvas = this.offCanvasRight;
this.offCanvasWidth = this.offCanvasRightWidth;
} else {
this.offCanvas = this.offCanvasLeft;
this.offCanvasWidth = this.offCanvasLeftWidth;
}
if (this.scroller) {
this.offCanvas.offsetHeight;
this.offCanvas.classList.add(CLASS_TRANSITIONING);
this.scroller.classList.add(CLASS_TRANSITIONING);
this.openPercentage(direction === 'left' ? 100 : -100);
}
return true;
},
toggle: function(directionOrOffCanvas) {
var direction = directionOrOffCanvas;
if (directionOrOffCanvas && directionOrOffCanvas.classList) {
direction = directionOrOffCanvas.classList.contains(CLASS_OFF_CANVAS_LEFT) ? 'left' : 'right';
this.refresh(directionOrOffCanvas);
}
if (!this.show(direction)) {
this.close();
}
}
});
//hash to offcanvas
var findOffCanvasContainer = function(target) {
parentNode = target.parentNode;
if (parentNode) {
if (parentNode.classList.contains(CLASS_OFF_CANVAS_WRAP)) {
return parentNode;
} else {
parentNode = parentNode.parentNode;
if (parentNode.classList.contains(CLASS_OFF_CANVAS_WRAP)) {
return parentNode;
}
}
}
};
var handle = function(event, target) {
if (target.tagName === 'A' && target.hash) {
var offcanvas = document.getElementById(target.hash.replace('#', ''));
if (offcanvas) {
var container = findOffCanvasContainer(offcanvas);
if (container) {
$.targets._container = container;
return offcanvas;
}
}
}
return false;
};
$.registerTarget({
name: name,
index: 60,
handle: handle,
target: false,
isReset: false,
isContinue: true
});
window.addEventListener('tap', function(e) {
if (!$.targets.offcanvas) {
return;
}
//TODO 此处类型的代码后续考虑统一优化(target机制),现在的实现费力不讨好
var target = e.target;
for (; target && target !== document; target = target.parentNode) {
if (target.tagName === 'A' && target.hash && target.hash === ('#' + $.targets.offcanvas.id)) {
e.detail && e.detail.gesture && e.detail.gesture.preventDefault(); //fixed hashchange
$($.targets._container).offCanvas().toggle($.targets.offcanvas);
$.targets.offcanvas = $.targets._container = null;
break;
}
}
});
$.fn.offCanvas = function(options) {
var offCanvasApis = [];
this.each(function() {
var offCanvasApi = null;
var self = this;
//hack old version
if (!self.classList.contains(CLASS_OFF_CANVAS_WRAP)) {
self = findOffCanvasContainer(self);
}
var id = self.getAttribute('data-offCanvas');
if (!id) {
id = ++$.uuid;
$.data[id] = offCanvasApi = new OffCanvas(self, options);
self.setAttribute('data-offCanvas', id);
} else {
offCanvasApi = $.data[id];
}
if (options === 'show' || options === 'close' || options === 'toggle') {
offCanvasApi.toggle();
}
offCanvasApis.push(offCanvasApi);
});
return offCanvasApis.length === 1 ? offCanvasApis[0] : offCanvasApis;
};
$.ready(function() {
$($.classSelector('.off-canvas-wrap')).offCanvas();
});
})(mui, window, document, 'offcanvas');