mirror of
https://github.com/mastodon/mastodon.git
synced 2025-07-15 16:58:14 +00:00
fix: Prevent scrolling behind menus and modals in Safari iOS (#35183)
This commit is contained in:
parent
dbb20f76a7
commit
c1ef1f62d5
|
@ -1,71 +1,30 @@
|
||||||
import { useLayoutEffect, useEffect, useState } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { createAppSelector, useAppSelector } from 'mastodon/store';
|
import { createAppSelector, useAppSelector } from 'mastodon/store';
|
||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|
||||||
|
|
||||||
const getShouldLockBodyScroll = createAppSelector(
|
const getShouldLockBodyScroll = createAppSelector(
|
||||||
[
|
[
|
||||||
(state) => state.navigation.open,
|
(state) => state.navigation.open,
|
||||||
(state) => state.modal.get('stack').size > 0,
|
(state) => state.modal.get('stack').size > 0,
|
||||||
],
|
],
|
||||||
(isMobileMenuOpen: boolean, isModalOpen: boolean) => {
|
(isMobileMenuOpen: boolean, isModalOpen: boolean) =>
|
||||||
return isMobileMenuOpen || isModalOpen;
|
isMobileMenuOpen || isModalOpen,
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component locks scrolling on the `body` element when
|
* This component locks scrolling on the body when
|
||||||
* `getShouldLockBodyScroll` returns true.
|
* `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 = () => {
|
export const BodyScrollLock: React.FC = () => {
|
||||||
const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll);
|
const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
document.body.classList.toggle('with-modals--active', shouldLockBodyScroll);
|
document.documentElement.classList.toggle(
|
||||||
|
'has-modal',
|
||||||
|
shouldLockBodyScroll,
|
||||||
|
);
|
||||||
}, [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;
|
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 'variables' as *;
|
||||||
@use 'functions' 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 {
|
body {
|
||||||
font-family: $font-sans-serif, sans-serif;
|
font-family: $font-sans-serif, sans-serif;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
|
@ -64,21 +78,6 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
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 {
|
&.player {
|
||||||
|
|
|
@ -2896,10 +2896,6 @@ a.account__display-name {
|
||||||
border-top: 1px solid var(--background-border-color);
|
border-top: 1px solid var(--background-border-color);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.with-modals--active & {
|
|
||||||
padding-right: var(--root-scrollbar-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-multiple-columns & {
|
.layout-multiple-columns & {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -3170,7 +3166,7 @@ a.account__display-name {
|
||||||
.navigation-panel {
|
.navigation-panel {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-inline-start: 1px solid var(--background-border-color);
|
border-inline-start: 1px solid var(--background-border-color);
|
||||||
height: 100vh;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-panel__banner,
|
.navigation-panel__banner,
|
||||||
|
@ -3228,6 +3224,7 @@ a.account__display-name {
|
||||||
.navigation-panel {
|
.navigation-panel {
|
||||||
width: 284px;
|
width: 284px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
&__menu {
|
&__menu {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user