/** * 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');