/** *
* @param {Object} $ * @param {Object} window */ (function($, window) { var CLASS_LEFT = $.className('left'); var CLASS_CENTER = $.className('center'); var CLASS_RIGHT = $.className('right'); var CLASS_PAGE = $.className('page'); var CLASS_PAGE_LEFT = $.className('page-left'); var CLASS_PAGE_CENTER = $.className('page-center'); var CLASS_NAVBAR_LEFT = $.className('navbar-left'); var CLASS_NAVBAR_CENTER = $.className('navbar-center'); var CLASS_PAGE_SHADOW = $.className('page-shadow'); var CLASS_TRANSITIONING = $.className('transitioning'); var SELECTOR_LEFT = '.' + CLASS_LEFT; var SELECTOR_CENTER = '.' + CLASS_CENTER; var SELECTOR_RIGHT = '.' + CLASS_RIGHT; var SELECTOR_ICON = $.classSelector('.icon'); var SELECTOR_NAVBAR = $.classSelector('.navbar'); var SELECTOR_NAVBAR_INNER = $.classSelector('.navbar-inner'); var SELECTOR_PAGES = $.classSelector('.pages'); var SELECTOR_BTN_NAV = $.classSelector('.btn-nav'); var SELECTOR_PAGE_LEFT = '.' + CLASS_PAGE_LEFT; var SELECTOR_PAGE_CENTER = '.' + CLASS_PAGE_CENTER; var SELECTOR_NAVBAR_LEFT = '.' + CLASS_NAVBAR_LEFT; var SELECTOR_NAVBAR_CENTER = '.' + CLASS_NAVBAR_CENTER; var View = $.Class.extend({ init: function(element, options) { this.view = this.element = element; this.options = $.extend({ animateNavbar: 'ios', //ios swipeBackPageActiveArea: 30, hardwareAccelerated: true }, options); this.navbars = this.view.querySelector(SELECTOR_NAVBAR); this.pages = this.view.querySelector(SELECTOR_PAGES); this.history = []; //history this.maxScrollX = this.view.offsetWidth; this.x = this.y = 0; this.translateZ = this.options.hardwareAccelerated ? ' translateZ(0)' : ''; this.ratio = 0; this.isBack = true; this.moved = this.dragging = false; this.activeNavbar = this.previousNavbar = null; this.activePage = this.previousPage = null; this._initPageEventMethod(); this._initDefaultPage(); this.navbars && this._initNavBar(); this.initEvent(); }, _initPageEventMethod: function() { var self = this; $.each(['onPageBeforeShow', 'onPageShow', 'onPageBeforeBack', 'onPageBack'], function(index, event) { self[event + 'Callbacks'] = {}; self[event] = function(page, callback) { var eventCallbacks = event + 'Callbacks'; if (!self[eventCallbacks].hasOwnProperty(page)) { self[eventCallbacks][page] = [callback]; } else { self[eventCallbacks][page].push(callback); } }; }); }, _initDefaultPage: function() { var defaultPage = document.querySelector(this.options.defaultPage); if (defaultPage) { this._appendPage(defaultPage); } else { throw new Error('defaultPage[' + this.options.defaultPage + '] does not exist'); } }, initEvent: function() { this.view.addEventListener('click', this); this.view.addEventListener('tap', this); this.pages.addEventListener('drag', this); this.pages.addEventListener('dragend', this); this.pages.addEventListener('webkitTransitionEnd', this); }, handleEvent: function(event) { switch (event.type) { case 'click': this._click(event); break; case 'tap': this._tap(event); break; case 'drag': this._drag(event); break; case 'dragend': this._dragend(event); break; case 'webkitTransitionEnd': this._webkitTransitionEnd(event); break; } }, shadow: function() { var shadow = document.createElement('div'); shadow.className = CLASS_PAGE_SHADOW; return shadow; }(), _removePage: function(page, navbar) { navbar && this._removeNavbar(page, navbar); document.body.appendChild(page); this._cleanPageClass(page); }, _prependPage: function(page) { var navbar = page.querySelector(SELECTOR_NAVBAR_INNER); navbar && this._prependNavbar(navbar); page.classList.add(CLASS_PAGE_LEFT); this.pages.insertBefore(page, this.pages.firstElementChild); }, _appendPage: function(page) { var navbar = page.querySelector(SELECTOR_NAVBAR_INNER); navbar && this._appendNavbar(navbar); page.classList.add(CLASS_PAGE_CENTER); this.pages.appendChild(page); }, _removeNavbar: function(page, navbar) { page.insertBefore(navbar, page.firstElementChild); this._cleanNavbarClass(navbar); }, _prependNavbar: function(navbar) { navbar.classList.add(CLASS_NAVBAR_LEFT); this.navbars.insertBefore(navbar, this.navbars.firstElementChild); }, _appendNavbar: function(navbar) { navbar.classList.add(CLASS_NAVBAR_CENTER); this.navbars.appendChild(navbar); }, _cleanPageClass: function(page) { page.classList.remove(CLASS_PAGE_CENTER); page.classList.remove(CLASS_PAGE_LEFT); }, _cleanNavbarClass: function(navbar) { navbar.classList.remove(CLASS_NAVBAR_CENTER); navbar.classList.remove(CLASS_NAVBAR_LEFT); }, _tap: function(event) { var target = event.target; for (; target && target !== document; target = target.parentNode) { if (target.tagName === 'A' && target.hash) { var page = document.getElementById(target.hash.replace('#', '')); if (page && page.classList.contains(CLASS_PAGE)) { event.stopPropagation(); event.detail.gesture.preventDefault(); this.go(target.hash); break; } } } }, _click: function(event) { var target = event.target; for (; target && target !== document; target = target.parentNode) { if (target.tagName === 'A' && target.hash) { var page = document.getElementById(target.hash.replace('#', '')); if (page && page.classList.contains(CLASS_PAGE)) { event.preventDefault(); break; } } } }, _cleanStyle: function(el) { if (el) { el.style.webkitTransform = ''; el.style.opacity = ''; } }, _isAnimateNavbarIOS: function() { return !$.os.android && this.options.animateNavbar === 'ios'; }, _webkitTransitionEnd: function(event) { this.dragging = this.moved = false; if (this.activePage !== event.target) { return; } this.isInTransition = false; this.shadow.parentNode === this.activePage && this.activePage.removeChild(this.shadow); this.previousPageClassList.remove(CLASS_TRANSITIONING); this.activePageClassList.remove(CLASS_TRANSITIONING); var self = this; if (this._isAnimateNavbarIOS() && this.previousNavElements && this.activeNavElements) { var isBack = this.isBack; $.each(this.previousNavElements, function(i, el) { el.classList.remove(CLASS_TRANSITIONING); isBack && self._cleanStyle(el); }); $.each(this.activeNavElements, function(i, el) { el.classList.remove(CLASS_TRANSITIONING); self._cleanStyle(el); }); if (this.previousNavBackIcon) { this.previousNavBackIcon.classList.remove(CLASS_TRANSITIONING); isBack && this._cleanStyle(this.previousNavBackIcon); } if (this.activeNavBackIcon) { this.activeNavBackIcon.classList.remove(CLASS_TRANSITIONING); this._cleanStyle(this.activeNavBackIcon); } } else { this.previousNavbar && this.previousNavbar.classList.remove(CLASS_TRANSITIONING); this.activeNavbar && this.activeNavbar.classList.remove(CLASS_TRANSITIONING); this._cleanStyle(this.previousNavbar); this._cleanStyle(this.activeNavbar); } this._cleanStyle(this.previousPage); this._cleanStyle(this.activePage); if (this.ratio <= 0.5) { return; } if (this.isBack) { this._removePage(this.activePage, this.activeNavbar); this.previousPageClassList.remove(CLASS_PAGE_LEFT); this.previousPageClassList.add(CLASS_PAGE_CENTER); if (this.previousNavbar) { this.previousNavbar.classList.remove(CLASS_NAVBAR_LEFT); this.previousNavbar.classList.add(CLASS_NAVBAR_CENTER); } if (this.history.length > 0) { this._prependPage(this.history.pop()); } this.navbars && this._initNavBar(); this._trigger('pageBack', this.activePage); this._trigger('pageShow', this.previousPage); } else { this.previousPageClassList.add(CLASS_PAGE_LEFT); this.activePageClassList.add(CLASS_PAGE_CENTER); this._trigger('pageShow', this.activePage); } }, _trigger: function(eventType, page) { var eventCallbacks = 'on' + eventType.charAt(0).toUpperCase() + eventType.slice(1) + 'Callbacks'; if (this[eventCallbacks].hasOwnProperty(page.id)) { var callbacks = this[eventCallbacks][page.id]; var event = new CustomEvent(eventType, { detail: { page: page }, bubbles: true, cancelable: true }); for (var len = callbacks.length; len--;) { callbacks[len].call(this, event); } } $.trigger(this.view, eventType, { page: page }); }, _initPageTransform: function() { this.previousPage = this.pages.querySelector(SELECTOR_PAGE_LEFT); this.activePage = this.pages.querySelector(SELECTOR_PAGE_CENTER); if (this.previousPage && this.activePage) { this.activePage.appendChild(this.shadow); this.previousPageClassList = this.previousPage.classList; this.activePageClassList = this.activePage.classList; this.previousPageStyle = this.previousPage.style; this.activePageStyle = this.activePage.style; this.previousPageClassList.remove(CLASS_TRANSITIONING); this.activePageClassList.remove(CLASS_TRANSITIONING); if (this.navbars) { this.previousNavbar = this.navbars.querySelector(SELECTOR_NAVBAR_LEFT); this.activeNavbar = this.navbars.querySelector(SELECTOR_NAVBAR_CENTER); if (this._isAnimateNavbarIOS() && this.previousNavbar && this.activeNavbar) { this.previousNavElements = this.previousNavbar.querySelectorAll(SELECTOR_LEFT + ',' + SELECTOR_CENTER + ',' + SELECTOR_RIGHT); this.activeNavElements = this.activeNavbar.querySelectorAll(SELECTOR_LEFT + ',' + SELECTOR_CENTER + ',' + SELECTOR_RIGHT); this.previousNavBackIcon = this.previousNavbar.querySelector(SELECTOR_LEFT + SELECTOR_BTN_NAV + ' ' + SELECTOR_ICON); this.activeNavBackIcon = this.activeNavbar.querySelector(SELECTOR_LEFT + SELECTOR_BTN_NAV + ' ' + SELECTOR_ICON); } } this.x = 0; this.dragging = true; return true; } return false; }, _initNavBar: function() { if (this._isAnimateNavbarIOS() && this.navbars) { var inners = this.navbars.querySelectorAll(SELECTOR_NAVBAR_INNER); var inner, left, right, center, leftWidth, rightWidth, centerWidth, noLeft, onRight, currLeft, diff, navbarWidth; for (var i = 0, len = inners.length; i < len; i++) { inner = inners[i]; left = inner.querySelector(SELECTOR_LEFT); right = inner.querySelector(SELECTOR_RIGHT); center = inner.querySelector(SELECTOR_CENTER); noLeft = !left; noRight = !right; leftWidth = noLeft ? 0 : left.offsetWidth; rightWidth = noRight ? 0 : right.offsetWidth; centerWidth = center ? center.offsetWidth : 0; navbarWidth = this.maxScrollX; onLeft = inner.classList.contains('navbar-left'); if (noRight) { currLeft = navbarWidth - centerWidth; } if (noLeft) { currLeft = 0; } if (!noLeft && !noRight) { currLeft = (navbarWidth - rightWidth - centerWidth + leftWidth) / 2; } var requiredLeft = (navbarWidth - centerWidth) / 2; if (navbarWidth - leftWidth - rightWidth > centerWidth) { if (requiredLeft < leftWidth) { requiredLeft = leftWidth; } if (requiredLeft + centerWidth > navbarWidth - rightWidth) { requiredLeft = navbarWidth - rightWidth - centerWidth; } diff = requiredLeft - currLeft; } else { diff = 0; } var centerLeft = diff; if (center) { center.style.marginLeft = -leftWidth + 'px'; center.mNavbarLeftOffset = -(currLeft + diff) + 30; //这个30是测出来的。后续要实际计算一下 center.mNavbarRightOffset = navbarWidth - currLeft - diff - centerWidth; } if (onLeft) center.style.webkitTransform = ('translate3d(' + center.mNavbarLeftOffset + 'px, 0, 0)'); if (!noLeft) { left.mNavbarLeftOffset = -leftWidth; left.mNavbarRightOffset = (navbarWidth - leftWidth) / 2; if (onLeft) left.style.webkitTransform = ('translate3d(' + left[0].mNavbarLeftOffset + 'px, 0, 0)'); } if (!noRight) { right.mNavbarLeftOffset = -(navbarWidth - rightWidth) / 2; right.mNavbarRightOffset = rightWidth; if (onLeft) right.style.webkitTransform = ('translate3d(' + right[0].mNavbarLeftOffset + 'px, 0, 0)'); } } } }, _drag: function(event) { if (this.isInTransition) { return; } var detail = event.detail; if (!this.dragging) { if (($.gestures.session.firstTouch.center.x - this.view.offsetLeft) < this.options.swipeBackPageActiveArea) { this.isBack = true; this._initPageTransform(); } } if (this.dragging) { var deltaX = 0; if (!this.moved) { //start deltaX = detail.deltaX; $.gestures.session.lockDirection = true; //锁定方向 $.gestures.session.startDirection = detail.direction; } else { //move deltaX = detail.deltaX - ($.gestures.session.prevTouch && $.gestures.session.prevTouch.deltaX || 0); } var newX = this.x + deltaX; if (newX < 0 || newX > this.maxScrollX) { newX = newX < 0 ? 0 : this.maxScrollX; } event.stopPropagation(); detail.gesture.preventDefault(); if (!this.requestAnimationFrame) { this._updateTranslate(); } this.moved = true; this.x = newX; this.y = 0; } }, _dragend: function(event) { if (!this.moved) { return; } event.stopPropagation(); var detail = event.detail; this._clearRequestAnimationFrame(); this._prepareTransition(); this.ratio = this.x / this.maxScrollX; if (this.ratio === 1 || this.ratio === 0) { $.trigger(this.activePage, 'webkitTransitionEnd'); return; } if (this.ratio > 0.5) { this.setTranslate(this.maxScrollX, 0); } else { this._cleanStyle(this.previousPage); this._cleanStyle(this.activePage); } }, _prepareTransition: function() { this.isInTransition = true; this.previousPageClassList.add(CLASS_TRANSITIONING); this.activePageClassList.add(CLASS_TRANSITIONING); var self = this; if (this.previousNavbar && this.activeNavbar) { this.previousNavbar.classList.add(CLASS_TRANSITIONING); this.activeNavbar.classList.add(CLASS_TRANSITIONING); if (this._isAnimateNavbarIOS() && this.previousNavElements && this.activeNavElements) { $.each(this.previousNavElements, function(i, el) { el.classList.add(CLASS_TRANSITIONING); self._cleanStyle(el); }); $.each(this.activeNavElements, function(i, el) { el.classList.add(CLASS_TRANSITIONING); self._cleanStyle(el); }); if (this.previousNavBackIcon) { this._cleanStyle(this.previousNavBackIcon); this.previousNavBackIcon.classList.add(CLASS_TRANSITIONING); } if (this.activeNavBackIcon) { this._cleanStyle(this.activeNavBackIcon); this.activeNavBackIcon.classList.add(CLASS_TRANSITIONING); } } } }, _clearRequestAnimationFrame: function() { if (this.requestAnimationFrame) { cancelAnimationFrame(this.requestAnimationFrame); this.requestAnimationFrame = null; } }, _getTranslateStr: function(x, y) { if (this.options.hardwareAccelerated) { return 'translate3d(' + x + 'px,' + y + 'px,0px) ' + this.translateZ; } return 'translate(' + x + 'px,' + y + 'px) '; }, _updateTranslate: function() { var self = this; if (self.x !== self.lastX || self.y !== self.lastY) { self.setTranslate(self.x, self.y); } self.requestAnimationFrame = requestAnimationFrame(function() { self._updateTranslate(); }); }, _setNavbarTranslate: function(x, y) { var percentage = x / this.maxScrollX; //only for ios if (this._isAnimateNavbarIOS()) { if (this.previousNavElements && this.activeNavElements) { this.animateNavbarByIOS(percentage); } } else { //pop-in this.activeNavbar.style.opacity = 1 - percentage * 1.3; this.previousNavbar.style.opacity = percentage * 1.3 - 0.3; } }, animateNavbarByIOS: function(percentage) { var i, len, style, el; for (i = 0, len = this.activeNavElements.length; i < len; i++) { el = this.activeNavElements[i]; style = el.style; style.opacity = (1 - percentage * (el.classList.contains(CLASS_LEFT) ? 3.5 : 1.3)); if (!el.classList.contains(CLASS_RIGHT)) { var activeNavTranslate = percentage * el.mNavbarRightOffset; el.style.webkitTransform = ('translate3d(' + activeNavTranslate + 'px,0,0)'); if (el.classList.contains(CLASS_LEFT) && this.activeNavBackIcon) { this.activeNavBackIcon.style.webkitTransform = ('translate3d(' + -activeNavTranslate + 'px,0,0)'); } } } for (i = 0, len = this.previousNavElements.length; i < len; i++) { el = this.previousNavElements[i]; style = el.style; style.opacity = percentage * 1.3 - 0.3; if (!el.classList.contains(CLASS_RIGHT)) { var previousNavTranslate = el.mNavbarLeftOffset * (1 - percentage); el.style.webkitTransform = ('translate3d(' + previousNavTranslate + 'px,0,0)'); if (el.classList.contains(CLASS_LEFT) && this.previousNavBackIcon) { this.previousNavBackIcon.style.webkitTransform = ('translate3d(' + -previousNavTranslate + 'px,0,0)'); } } } }, setTranslate: function(x, y) { this.x = x; this.y = y; this.previousPage.style.opacity = 0.9 + 0.1 * x / this.maxScrollX; this.previousPage.style['webkitTransform'] = this._getTranslateStr((x / 6 - this.maxScrollX / 6), y); this.activePage.style['webkitTransform'] = this._getTranslateStr(x, y); this.navbars && this._setNavbarTranslate(x, y); this.lastX = this.x; this.lastY = this.y; }, canBack: function() { return this.pages.querySelector(SELECTOR_PAGE_LEFT); }, back: function() { if (this.isInTransition) { return; } this.isBack = true; this.ratio = 1; if (this._initPageTransform()) { this._trigger('pageBeforeBack', this.activePage); this._trigger('pageBeforeShow', this.previousPage); this._prepareTransition(); this.previousPage.offsetHeight; this.activePage.offsetHeight; this.setTranslate(this.maxScrollX, 0); } }, go: function(pageSelector) { if (this.isInTransition) { return; } var nextPage = document.querySelector(pageSelector); if (nextPage) { var previousPage = this.pages.querySelector(SELECTOR_PAGE_LEFT); var activePage = this.pages.querySelector(SELECTOR_PAGE_CENTER); var previousNavbar; var activeNavbar; if (this.navbars) { previousNavbar = this.navbars.querySelector(SELECTOR_NAVBAR_LEFT); activeNavbar = this.navbars.querySelector(SELECTOR_NAVBAR_CENTER); } if (activeNavbar) { activeNavbar.classList.remove(CLASS_NAVBAR_CENTER); activeNavbar.classList.add(CLASS_NAVBAR_LEFT); } if (previousPage) { this._removePage(previousPage, previousNavbar); this.history.push(previousPage); //add to history } if (activePage) { activePage.classList.remove(CLASS_PAGE_CENTER); activePage.style.webkitTransform = 'translate3d(0,0,0)'; activePage.classList.add(CLASS_PAGE_LEFT); } nextPage.style.webkitTransform = 'translate3d(100%,0,0)'; this._appendPage(nextPage); nextPage.appendChild(this.shadow); //shadow nextPage.offsetHeight; //force this.isBack = false; this.ratio = 1; this._initPageTransform(); this.navbars && this._initNavBar(); this.navbars && this._setNavbarTranslate(this.maxScrollX, 0); //force this.previousPage.offsetHeight; this.activePage.offsetHeight; if (this.navbars) { this.previousNavbar.offsetHeight; this.activeNavbar.offsetHeight; } this._trigger('pageBeforeShow', this.activePage); this._prepareTransition(); this.setTranslate(0, 0); } } }); $.fn.view = function(options) { var self = this[0]; var viewApi = null; var id = self.getAttribute('data-view'); if (!id) { id = ++$.uuid; $.data[id] = viewApi = new View(self, options); self.setAttribute('data-view', id); } else { viewApi = $.data[id]; } return viewApi; } })(mui, window);