123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- /**
- * <div id="app" class="mui-views">
- <div class="mui-view">
- <div class="mui-navbar">
- </div>
- <div class="mui-pages">
- </div>
- </div>
- </div>
- * @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);
|