From 245b9d27bc10a4d8c8eb64add06b653f12763f56 Mon Sep 17 00:00:00 2001 From: Kim <1877318+kimsible@users.noreply.github.com> Date: Mon, 17 Aug 2020 10:17:54 +0200 Subject: [PATCH] On touchscreens add content overlay for opened menu (#3088) * Overflow:hidden on touchscreen when modal-open * Do not display menu by default on touchscreens * Add content overlay on touchscreens when menu is opened * Fix zIndex overlay for search infos * On touchscreens close menu on swipe left Co-authored-by: kimsible --- client/src/app/app.component.ts | 4 +- client/src/app/core/menu/menu.service.ts | 47 +++++++++++---- .../app/core/routing/menu-guard.service.ts | 2 +- .../src/app/core/wrappers/screen.service.ts | 58 +++++++++++++++++++ client/src/sass/bootstrap.scss | 23 ++++++++ 5 files changed, 119 insertions(+), 15 deletions(-) diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index b8af4e2c7..5b0439e6b 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -180,8 +180,8 @@ export class AppComponent implements OnInit, AfterViewInit { eventsObs.pipe( filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), - filter(() => this.screenService.isInSmallView()) - ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page + filter(() => this.screenService.isInSmallView() || !!this.screenService.isInTouchScreen()) + ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page } private injectBroadcastMessage () { diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index ef5271f97..671ee3e4f 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts @@ -12,22 +12,45 @@ export class MenuService { constructor ( private screenService: ScreenService ) { - // Do not display menu on small screens - if (this.screenService.isInSmallView()) { - this.isMenuDisplayed = false + // Do not display menu on small or touch screens + if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) { + this.setMenuDisplay(false) + } + + this.handleWindowResize() + } + + toggleMenu () { + this.setMenuDisplay(!this.isMenuDisplayed) + this.isMenuChangedByUser = true + } + + setMenuDisplay (display: boolean) { + this.isMenuDisplayed = display + + // On touch screens, lock body scroll and display content overlay when memu is opened + if (this.screenService.isInTouchScreen()) { + if (this.isMenuDisplayed) { + document.body.classList.add('menu-open') + this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) }) + } else { + document.body.classList.remove('menu-open') + } + } + } + + onResize () { + this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser + } + + private handleWindowResize () { + // On touch screens, do not handle window resize event since opened menu is handled with a content overlay + if (this.screenService.isInTouchScreen()) { + return } fromEvent(window, 'resize') .pipe(debounceTime(200)) .subscribe(() => this.onResize()) } - - toggleMenu () { - this.isMenuDisplayed = !this.isMenuDisplayed - this.isMenuChangedByUser = true - } - - onResize () { - this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser - } } diff --git a/client/src/app/core/routing/menu-guard.service.ts b/client/src/app/core/routing/menu-guard.service.ts index 9df285635..501e009c0 100644 --- a/client/src/app/core/routing/menu-guard.service.ts +++ b/client/src/app/core/routing/menu-guard.service.ts @@ -15,7 +15,7 @@ abstract class MenuGuard implements CanActivate, CanDeactivate { // small screens already have the site-wide onResize from screenService // > medium screens have enough space to fit the administrative menus if (!this.screen.isInMobileView() && this.screen.isInMediumView()) { - this.menu.isMenuDisplayed = this.display + this.menu.setMenuDisplay(this.display) } return true } diff --git a/client/src/app/core/wrappers/screen.service.ts b/client/src/app/core/wrappers/screen.service.ts index 96fb7ae57..88cf662b3 100644 --- a/client/src/app/core/wrappers/screen.service.ts +++ b/client/src/app/core/wrappers/screen.service.ts @@ -54,6 +54,64 @@ export class ScreenService { return this.windowInnerWidth } + // https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android + onFingerSwipe (direction: 'left' | 'right' | 'up' | 'down', action: () => void, removeEventOnEnd = true) { + let touchDownClientX: number + let touchDownClientY: number + + const onTouchStart = (event: TouchEvent) => { + const firstTouch = event.touches[0] + touchDownClientX = firstTouch.clientX + touchDownClientY = firstTouch.clientY + } + + const onTouchMove = (event: TouchEvent) => { + if (!touchDownClientX || !touchDownClientY) { + return + } + + const touchUpClientX = event.touches[0].clientX + const touchUpClientY = event.touches[0].clientY + + const touchClientX = Math.abs(touchDownClientX - touchUpClientX) + const touchClientY = Math.abs(touchDownClientY - touchUpClientY) + + if (touchClientX > touchClientY) { + if (touchClientX > 0) { + if (direction === 'left') { + if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) + action() + } + } else { + if (direction === 'right') { + if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) + action() + } + } + } else { + if (touchClientY > 0) { + if (direction === 'up') { + if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) + action() + } + } else { + if (direction === 'down') { + if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) + action() + } + } + } + } + + document.addEventListener('touchstart', onTouchStart, false) + document.addEventListener('touchmove', onTouchMove, false) + } + + private removeFingerSwipeEventListener (onTouchStart: (event: TouchEvent) => void, onTouchMove: (event: TouchEvent) => void) { + document.removeEventListener('touchstart', onTouchStart) + document.removeEventListener('touchmove', onTouchMove) + } + private refreshWindowInnerWidth () { this.lastFunctionCallTime = new Date().getTime() diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 308a28658..a3b60198c 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss @@ -150,6 +150,29 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; width: 100vw; // Make sure the content fits all the available width } +// On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll +@media (hover: none) and (pointer: coarse) { + .modal-open, .menu-open { + overflow: hidden !important; + } + + // On touchscreen devices display content overlay when opened menu + .menu-open { + .main-col { + &::before { + background-color: black; + width: 100vw; + height: 100vh; + opacity: 0.75; + content: ''; + display: block; + position: fixed; + z-index: z('header') - 1; + } + } + } +} + // Nav customizations .nav .nav-link { display: flex !important;