fix: Prevent scrolling behind menus and modals in Safari iOS (#35183)
This commit is contained in:
		
					parent
					
						
							
								dbb20f76a7
							
						
					
				
			
			
				commit
				
					
						c1ef1f62d5
					
				
			
		
					 4 changed files with 24 additions and 88 deletions
				
			
		|  | @ -1,71 +1,30 @@ | |||
| import { useLayoutEffect, useEffect, useState } from 'react'; | ||||
| import { useLayoutEffect } from 'react'; | ||||
| 
 | ||||
| import { createAppSelector, useAppSelector } from 'mastodon/store'; | ||||
| import { getScrollbarWidth } from 'mastodon/utils/scrollbar'; | ||||
| 
 | ||||
| const getShouldLockBodyScroll = createAppSelector( | ||||
|   [ | ||||
|     (state) => state.navigation.open, | ||||
|     (state) => state.modal.get('stack').size > 0, | ||||
|   ], | ||||
|   (isMobileMenuOpen: boolean, isModalOpen: boolean) => { | ||||
|     return isMobileMenuOpen || isModalOpen; | ||||
|   }, | ||||
|   (isMobileMenuOpen: boolean, isModalOpen: boolean) => | ||||
|     isMobileMenuOpen || isModalOpen, | ||||
| ); | ||||
| 
 | ||||
| /** | ||||
|  * This component locks scrolling on the `body` element when | ||||
|  * This component locks scrolling on the body when | ||||
|  * `getShouldLockBodyScroll` returns true. | ||||
|  * | ||||
|  * The scrollbar width is taken into account and written to | ||||
|  * a CSS custom property `--root-scrollbar-width` | ||||
|  */ | ||||
| 
 | ||||
| export const BodyScrollLock: React.FC = () => { | ||||
|   const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll); | ||||
| 
 | ||||
|   useLayoutEffect(() => { | ||||
|     document.body.classList.toggle('with-modals--active', shouldLockBodyScroll); | ||||
|     document.documentElement.classList.toggle( | ||||
|       'has-modal', | ||||
|       shouldLockBodyScroll, | ||||
|     ); | ||||
|   }, [shouldLockBodyScroll]); | ||||
| 
 | ||||
|   const [scrollbarWidth, setScrollbarWidth] = useState(() => | ||||
|     getScrollbarWidth(), | ||||
|   ); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const handleResize = () => { | ||||
|       setScrollbarWidth(getScrollbarWidth()); | ||||
|     }; | ||||
|     window.addEventListener('resize', handleResize, { passive: true }); | ||||
|     return () => { | ||||
|       window.removeEventListener('resize', handleResize); | ||||
|     }; | ||||
|   }, []); | ||||
| 
 | ||||
|   // Inject style element to make scrollbar width available
 | ||||
|   // as CSS custom property
 | ||||
|   useLayoutEffect(() => { | ||||
|     const nonce = document | ||||
|       .querySelector('meta[name=style-nonce]') | ||||
|       ?.getAttribute('content'); | ||||
| 
 | ||||
|     if (nonce) { | ||||
|       const styleEl = document.createElement('style'); | ||||
|       styleEl.nonce = nonce; | ||||
|       styleEl.innerHTML = ` | ||||
|         :root { | ||||
|           --root-scrollbar-width: ${scrollbarWidth}px; | ||||
|         } | ||||
|       `;
 | ||||
|       document.head.appendChild(styleEl); | ||||
| 
 | ||||
|       return () => { | ||||
|         document.head.removeChild(styleEl); | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     return () => ''; | ||||
|   }, [scrollbarWidth]); | ||||
| 
 | ||||
|   return null; | ||||
| }; | ||||
|  |  | |||
|  | @ -1,19 +0,0 @@ | |||
| import { isMobile } from '../is_mobile'; | ||||
| 
 | ||||
| export const getScrollbarWidth = () => { | ||||
|   if (isMobile(window.innerWidth)) { | ||||
|     return 0; | ||||
|   } | ||||
|   const outer = document.createElement('div'); | ||||
|   outer.style.visibility = 'hidden'; | ||||
|   outer.style.overflow = 'scroll'; | ||||
|   document.body.appendChild(outer); | ||||
| 
 | ||||
|   const inner = document.createElement('div'); | ||||
|   outer.appendChild(inner); | ||||
| 
 | ||||
|   const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; | ||||
|   outer.remove(); | ||||
| 
 | ||||
|   return scrollbarWidth; | ||||
| }; | ||||
|  | @ -1,6 +1,20 @@ | |||
| @use 'variables' as *; | ||||
| @use 'functions' as *; | ||||
| 
 | ||||
| html.has-modal { | ||||
|   &, | ||||
|   body { | ||||
|     touch-action: none; | ||||
|     overscroll-behavior: none; | ||||
|     -webkit-overflow-scrolling: auto; | ||||
|     scrollbar-gutter: stable; | ||||
|   } | ||||
| 
 | ||||
|   body { | ||||
|     overflow: hidden !important; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   font-family: $font-sans-serif, sans-serif; | ||||
|   background: var(--background-color); | ||||
|  | @ -64,21 +78,6 @@ body { | |||
|       height: 100%; | ||||
|       padding-bottom: env(safe-area-inset-bottom); | ||||
|     } | ||||
| 
 | ||||
|     &.with-modals--active { | ||||
|       overflow-y: hidden; | ||||
|       overscroll-behavior: none; | ||||
|       margin-right: var(--root-scrollbar-width, 0); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.with-modals { | ||||
|     overflow-x: hidden; | ||||
|     overflow-y: scroll; | ||||
| 
 | ||||
|     &--active { | ||||
|       overflow-y: hidden; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.player { | ||||
|  |  | |||
|  | @ -2896,10 +2896,6 @@ a.account__display-name { | |||
|   border-top: 1px solid var(--background-border-color); | ||||
|   box-sizing: border-box; | ||||
| 
 | ||||
|   .with-modals--active & { | ||||
|     padding-right: var(--root-scrollbar-width); | ||||
|   } | ||||
| 
 | ||||
|   .layout-multiple-columns & { | ||||
|     display: none; | ||||
|   } | ||||
|  | @ -3170,7 +3166,7 @@ a.account__display-name { | |||
|     .navigation-panel { | ||||
|       margin: 0; | ||||
|       border-inline-start: 1px solid var(--background-border-color); | ||||
|       height: 100vh; | ||||
|       height: 100dvh; | ||||
|     } | ||||
| 
 | ||||
|     .navigation-panel__banner, | ||||
|  | @ -3228,6 +3224,7 @@ a.account__display-name { | |||
|     .navigation-panel { | ||||
|       width: 284px; | ||||
|       overflow-y: auto; | ||||
|       scrollbar-width: thin; | ||||
| 
 | ||||
|       &__menu { | ||||
|         flex-shrink: 0; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue