UNPKG

@pi0/framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

387 lines (355 loc) 16.5 kB
import $ from 'dom7'; import History from '../../utils/history'; import Support from '../../utils/support'; import Device from '../../utils/device'; import Utils from '../../utils/utils'; function SwipeBack(r) { const router = r; const { $el, $navbarEl, app } = router; let isTouched = false; let isMoved = false; const touchesStart = {}; let isScrolling; let currentPage = []; let previousPage = []; let viewContainerWidth; let touchesDiff; let allowViewTouchMove = true; let touchStartTime; let currentNavbar = []; let previousNavbar = []; let currentNavElements; let previousNavElements; let activeNavBackIcon; let activeNavBackIconText; let previousNavBackIcon; // let previousNavBackIconText; let dynamicNavbar; let separateNavbar; let pageShadow; let pageOpacity; let navbarWidth; function handleTouchStart(e) { if (!allowViewTouchMove || !router.params.iosSwipeBack || isTouched || (app.swipeout && app.swipeout.el) || !router.allowPageChange) return; if ($(e.target).closest('.range-slider, .calendar-months').length > 0) return; isMoved = false; isTouched = true; isScrolling = undefined; touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX; touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY; touchStartTime = Utils.now(); dynamicNavbar = router.dynamicNavbar; separateNavbar = router.separateNavbar; } function handleTouchMove(e) { if (!isTouched) return; const pageX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX; const pageY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY; if (typeof isScrolling === 'undefined') { isScrolling = !!(isScrolling || Math.abs(pageY - touchesStart.y) > Math.abs(pageX - touchesStart.x)); } if (isScrolling || e.f7PreventSwipeBack || app.preventSwipeBack) { isTouched = false; return; } if (!isMoved) { // Calc values during first move fired let cancel = false; const target = $(e.target); const swipeout = target.closest('.swipeout'); if (swipeout.length > 0) { if (!app.rtl && swipeout.find('.swipeout-actions-left').length > 0) cancel = true; if (app.rtl && swipeout.find('.swipeout-actions-right').length > 0) cancel = true; } currentPage = target.closest('.page'); if (currentPage.hasClass('no-swipeback')) cancel = true; previousPage = $el.find('.page-previous:not(.stacked)'); let notFromBorder = touchesStart.x - $el.offset().left > router.params.iosSwipeBackActiveArea; viewContainerWidth = $el.width(); if (app.rtl) { notFromBorder = touchesStart.x < ($el.offset().left - $el[0].scrollLeft) + (viewContainerWidth - router.params.iosSwipeBackActiveArea); } else { notFromBorder = touchesStart.x - $el.offset().left > router.params.iosSwipeBackActiveArea; } if (notFromBorder) cancel = true; if (previousPage.length === 0 || currentPage.length === 0) cancel = true; if (cancel) { isTouched = false; return; } if (router.params.iosSwipeBackAnimateShadow) { pageShadow = currentPage.find('.page-shadow-effect'); if (pageShadow.length === 0) { pageShadow = $('<div class="page-shadow-effect"></div>'); currentPage.append(pageShadow); } } if (router.params.iosSwipeBackAnimateOpacity) { pageOpacity = previousPage.find('.page-opacity-effect'); if (pageOpacity.length === 0) { pageOpacity = $('<div class="page-opacity-effect"></div>'); previousPage.append(pageOpacity); } } if (dynamicNavbar) { if (separateNavbar) { currentNavbar = $navbarEl.find('.navbar-current:not(.stacked)'); previousNavbar = $navbarEl.find('.navbar-previous:not(.stacked)'); } else { currentNavbar = currentPage.children('.navbar').children('.navbar-inner'); previousNavbar = previousPage.children('.navbar').children('.navbar-inner'); } navbarWidth = $navbarEl[0].offsetWidth; currentNavElements = currentNavbar.children('.left, .title, .right, .subnavbar, .fading'); previousNavElements = previousNavbar.children('.left, .title, .right, .subnavbar, .fading'); if (router.params.iosAnimateNavbarBackIcon) { if (currentNavbar.hasClass('sliding')) { activeNavBackIcon = currentNavbar.children('.left').find('.back .icon'); activeNavBackIconText = currentNavbar.children('.left').find('.back span').eq(0); } else { activeNavBackIcon = currentNavbar.children('.left.sliding').find('.back .icon'); activeNavBackIconText = currentNavbar.children('.left.sliding').find('.back span').eq(0); } if (previousNavbar.hasClass('sliding')) { previousNavBackIcon = previousNavbar.children('.left').find('.back .icon'); // previousNavBackIconText = previousNavbar.children('left').find('.back span').eq(0); } else { previousNavBackIcon = previousNavbar.children('.left.sliding').find('.back .icon'); // previousNavBackIconText = previousNavbar.children('.left.sliding').find('.back span').eq(0); } } } // Close/Hide Any Picker if ($('.sheet.modal-in').length > 0 && app.sheet) { app.sheet.close($('.sheet.modal-in')); } } e.f7PreventPanelSwipe = true; isMoved = true; e.preventDefault(); // RTL inverter const inverter = app.rtl ? -1 : 1; // Touches diff touchesDiff = (pageX - touchesStart.x - router.params.iosSwipeBackThreshold) * inverter; if (touchesDiff < 0) touchesDiff = 0; const percentage = touchesDiff / viewContainerWidth; // Swipe Back Callback const callbackData = { percentage, currentPageEl: currentPage[0], previousPageEl: previousPage[0], currentNavbarEl: currentNavbar[0], previousNavbarEl: previousNavbar[0], }; $el.trigger('swipeback:move', callbackData); router.emit('swipeBackMove', callbackData); // Transform pages let currentPageTranslate = touchesDiff * inverter; let previousPageTranslate = ((touchesDiff / 5) - (viewContainerWidth / 5)) * inverter; if (Device.pixelRatio === 1) { currentPageTranslate = Math.round(currentPageTranslate); previousPageTranslate = Math.round(previousPageTranslate); } currentPage.transform(`translate3d(${currentPageTranslate}px,0,0)`); if (router.params.iosSwipeBackAnimateShadow) pageShadow[0].style.opacity = 1 - (1 * percentage); previousPage.transform(`translate3d(${previousPageTranslate}px,0,0)`); if (router.params.iosSwipeBackAnimateOpacity) pageOpacity[0].style.opacity = 1 - (1 * percentage); // Dynamic Navbars Animation if (dynamicNavbar) { currentNavElements.each((index, navEl) => { const $navEl = $(navEl); if (!$navEl.is('.subnavbar')) $navEl[0].style.opacity = (1 - (percentage * 1.3)); if ($navEl[0].className.indexOf('sliding') >= 0 || currentNavbar.hasClass('sliding')) { let activeNavTranslate = percentage * $navEl[0].f7NavbarRightOffset; if (Device.pixelRatio === 1) activeNavTranslate = Math.round(activeNavTranslate); $navEl.transform(`translate3d(${activeNavTranslate}px,0,0)`); if (router.params.iosAnimateNavbarBackIcon) { if ($navEl[0].className.indexOf('left') >= 0 && activeNavBackIcon.length > 0) { let iconTranslate = -activeNavTranslate; if (!separateNavbar) { iconTranslate -= navbarWidth * percentage; } activeNavBackIcon.transform(`translate3d(${iconTranslate}px,0,0)`); } } } }); previousNavElements.each((index, navEl) => { const $navEl = $(navEl); if (!$navEl.is('.subnavbar')) $navEl[0].style.opacity = (percentage * 1.3) - 0.3; if ($navEl[0].className.indexOf('sliding') >= 0 || previousNavbar.hasClass('sliding')) { let previousNavTranslate = $navEl[0].f7NavbarLeftOffset * (1 - percentage); if ($navEl[0].className.indexOf('title') >= 0 && activeNavBackIcon && activeNavBackIcon.length && activeNavBackIconText.length) { previousNavTranslate = ($navEl[0].f7NavbarLeftOffset + activeNavBackIconText[0].offsetLeft) * (1 - percentage); } else { previousNavTranslate = $navEl[0].f7NavbarLeftOffset * (1 - percentage); } if (Device.pixelRatio === 1) previousNavTranslate = Math.round(previousNavTranslate); $navEl.transform(`translate3d(${previousNavTranslate}px,0,0)`); if (router.params.iosAnimateNavbarBackIcon) { if ($navEl[0].className.indexOf('left') >= 0 && previousNavBackIcon.length > 0) { let iconTranslate = -previousNavTranslate; if (!separateNavbar) { iconTranslate += (navbarWidth / 5) * (1 - percentage); } previousNavBackIcon.transform(`translate3d(${iconTranslate}px,0,0)`); } } } }); } } function handleTouchEnd() { if (!isTouched || !isMoved) { isTouched = false; isMoved = false; return; } isTouched = false; isMoved = false; if (touchesDiff === 0) { $([currentPage[0], previousPage[0]]).transform(''); if (dynamicNavbar) { currentNavElements.transform('').css({ opacity: '' }); previousNavElements.transform('').css({ opacity: '' }); if (activeNavBackIcon && activeNavBackIcon.length > 0) activeNavBackIcon.transform(''); if (previousNavBackIcon && activeNavBackIcon.length > 0) previousNavBackIcon.transform(''); } return; } const timeDiff = Utils.now() - touchStartTime; let pageChanged = false; // Swipe back to previous page if ( (timeDiff < 300 && touchesDiff > 10) || (timeDiff >= 300 && touchesDiff > viewContainerWidth / 2) ) { currentPage.removeClass('page-current').addClass('page-next'); previousPage.removeClass('page-previous').addClass('page-current'); if (pageShadow) pageShadow[0].style.opacity = ''; if (pageOpacity) pageOpacity[0].style.opacity = ''; if (dynamicNavbar) { currentNavbar.removeClass('navbar-current').addClass('navbar-next'); previousNavbar.removeClass('navbar-previous').addClass('navbar-current'); } pageChanged = true; } // Reset custom styles // Add transitioning class for transition-duration $([currentPage[0], previousPage[0]]).addClass('page-transitioning').transform(''); if (dynamicNavbar) { currentNavElements.css({ opacity: '' }) .each((navElIndex, navEl) => { const translate = pageChanged ? navEl.f7NavbarRightOffset : 0; const sliding = $(navEl); let iconTranslate = pageChanged ? -translate : 0; if (!separateNavbar && pageChanged) iconTranslate -= navbarWidth; sliding.transform(`translate3d(${translate}px,0,0)`); if (router.params.iosAnimateNavbarBackIcon) { if (sliding.hasClass('left') && activeNavBackIcon.length > 0) { activeNavBackIcon.addClass('navbar-transitioning').transform(`translate3d(${iconTranslate}px,0,0)`); } } }).addClass('navbar-transitioning'); previousNavElements.transform('').css({ opacity: '' }).each((navElIndex, navEl) => { const translate = pageChanged ? 0 : navEl.f7NavbarLeftOffset; const sliding = $(navEl); let iconTranslate = pageChanged ? 0 : -translate; if (!separateNavbar && !pageChanged) iconTranslate += navbarWidth / 5; sliding.transform(`translate3d(${translate}px,0,0)`); if (router.params.iosAnimateNavbarBackIcon) { if (sliding.hasClass('left') && previousNavBackIcon.length > 0) { previousNavBackIcon.addClass('navbar-transitioning').transform(`translate3d(${iconTranslate}px,0,0)`); } } }).addClass('navbar-transitioning'); } allowViewTouchMove = false; router.allowPageChange = false; // Swipe Back Callback const callbackData = { currentPage: currentPage[0], previousPage: previousPage[0], currentNavbar: currentNavbar[0], previousNavbar: previousNavbar[0], }; if (pageChanged) { // Update Route router.currentRoute = previousPage[0].f7Page.route; router.currentPage = previousPage[0]; // Page before animation callback router.pageCallback('beforeOut', currentPage, currentNavbar, 'current', 'next', { route: currentPage[0].f7Page.route }); router.pageCallback('beforeIn', previousPage, previousNavbar, 'previous', 'current', { route: previousPage[0].f7Page.route }); $el.trigger('swipeback:beforechange', callbackData); router.emit('swipeBackBeforeChange', callbackData); } else { $el.trigger('swipeback:beforereset', callbackData); router.emit('swipeBackBeforeReset', callbackData); } currentPage.transitionEnd(() => { $([currentPage[0], previousPage[0]]).removeClass('page-transitioning'); if (dynamicNavbar) { currentNavElements.removeClass('navbar-transitioning').css({ opacity: '' }).transform(''); previousNavElements.removeClass('navbar-transitioning').css({ opacity: '' }).transform(''); if (activeNavBackIcon && activeNavBackIcon.length > 0) activeNavBackIcon.removeClass('navbar-transitioning'); if (previousNavBackIcon && previousNavBackIcon.length > 0) previousNavBackIcon.removeClass('navbar-transitioning'); } allowViewTouchMove = true; router.allowPageChange = true; if (pageChanged) { // Update History if (router.history.length === 1) { router.history.unshift(router.url); } router.history.pop(); router.saveHistory(); // Update push state if (router.params.pushState) { History.back(); } // Page after animation callback router.pageCallback('afterOut', currentPage, currentNavbar, 'current', 'next', { route: currentPage[0].f7Page.route }); router.pageCallback('afterIn', previousPage, previousNavbar, 'previous', 'current', { route: previousPage[0].f7Page.route }); // Remove Old Page if (router.params.stackPages && router.initialPages.indexOf(currentPage[0]) >= 0) { currentPage.addClass('stacked'); if (separateNavbar) { currentNavbar.addClass('stacked'); } } else { router.pageCallback('beforeRemove', currentPage, currentNavbar, 'next'); router.removePage(currentPage); if (separateNavbar) { router.removeNavbar(currentNavbar); } } $el.trigger('swipeback:afterchange', callbackData); router.emit('swipeBackAfterChange', callbackData); router.emit('routeChanged', router.currentRoute, router.previousRoute, router); if (router.params.preloadPreviousPage) { router.back(router.history[router.history.length - 2], { preload: true }); } } else { $el.trigger('swipeback:afterreset', callbackData); router.emit('swipeBackAfterReset', callbackData); } if (pageShadow && pageShadow.length > 0) pageShadow.remove(); if (pageOpacity && pageOpacity.length > 0) pageOpacity.remove(); }); } function attachEvents() { const passiveListener = (app.touchEvents.start === 'touchstart' && Support.passiveListener) ? { passive: true, capture: false } : false; $el.on(app.touchEvents.start, handleTouchStart, passiveListener); app.on('touchmove:active', handleTouchMove); app.on('touchend:passive', handleTouchEnd); } function detachEvents() { const passiveListener = (app.touchEvents.start === 'touchstart' && Support.passiveListener) ? { passive: true, capture: false } : false; $el.off(app.touchEvents.start, handleTouchStart, passiveListener); app.off('touchmove:active', handleTouchMove); app.off('touchend:passive', handleTouchEnd); } attachEvents(); router.on('routerDestroy', detachEvents); } export default SwipeBack;