mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-07 10:22:48 +00:00
fix: Prevent content scrolling behind main menu (part 1) (#35173)
This commit is contained in:
parent
c52848b444
commit
c6dddbb66e
|
@ -18,6 +18,7 @@ import initialState, { title as siteTitle } from 'mastodon/initial_state';
|
||||||
import { IntlProvider } from 'mastodon/locales';
|
import { IntlProvider } from 'mastodon/locales';
|
||||||
import { store } from 'mastodon/store';
|
import { store } from 'mastodon/store';
|
||||||
import { isProduction } from 'mastodon/utils/environment';
|
import { isProduction } from 'mastodon/utils/environment';
|
||||||
|
import { BodyScrollLock } from 'mastodon/features/ui/components/body_scroll_lock';
|
||||||
|
|
||||||
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
|
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ export default class Mastodon extends PureComponent {
|
||||||
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||||
<Route path='/' component={UI} />
|
<Route path='/' component={UI} />
|
||||||
</ScrollContext>
|
</ScrollContext>
|
||||||
|
<BodyScrollLock />
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
|
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
|
||||||
|
|
|
@ -14,7 +14,6 @@ import MediaModal from 'mastodon/features/ui/components/media_modal';
|
||||||
import { Video } from 'mastodon/features/video';
|
import { Video } from 'mastodon/features/video';
|
||||||
import { IntlProvider } from 'mastodon/locales';
|
import { IntlProvider } from 'mastodon/locales';
|
||||||
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|
||||||
|
|
||||||
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
|
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
|
||||||
|
|
||||||
|
@ -34,9 +33,6 @@ export default class MediaContainer extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index, lang) => {
|
handleOpenMedia = (media, index, lang) => {
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
|
|
||||||
this.setState({ media, index, lang });
|
this.setState({ media, index, lang });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,16 +41,10 @@ export default class MediaContainer extends PureComponent {
|
||||||
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
||||||
const mediaList = fromJS(media);
|
const mediaList = fromJS(media);
|
||||||
|
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
|
|
||||||
this.setState({ media: mediaList, lang, options });
|
this.setState({ media: mediaList, lang, options });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCloseMedia = () => {
|
handleCloseMedia = () => {
|
||||||
document.body.classList.remove('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = '0';
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
media: null,
|
media: null,
|
||||||
index: null,
|
index: null,
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { useLayoutEffect, useEffect, useState } 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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component locks scrolling on the `body` element 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);
|
||||||
|
}, [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;
|
||||||
|
};
|
|
@ -20,7 +20,6 @@ import {
|
||||||
IgnoreNotificationsModal,
|
IgnoreNotificationsModal,
|
||||||
AnnualReportModal,
|
AnnualReportModal,
|
||||||
} from 'mastodon/features/ui/util/async-components';
|
} from 'mastodon/features/ui/util/async-components';
|
||||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
|
||||||
|
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
|
|
||||||
|
@ -90,20 +89,6 @@ export default class ModalRoot extends PureComponent {
|
||||||
backgroundColor: null,
|
backgroundColor: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
getSnapshotBeforeUpdate () {
|
|
||||||
return { visible: !!this.props.type };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState, { visible }) {
|
|
||||||
if (visible) {
|
|
||||||
document.body.classList.add('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('with-modals--active');
|
|
||||||
document.documentElement.style.marginRight = '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackgroundColor = color => {
|
setBackgroundColor = color => {
|
||||||
this.setState({ backgroundColor: color });
|
this.setState({ backgroundColor: color });
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { isMobile } from '../is_mobile';
|
import { isMobile } from '../is_mobile';
|
||||||
|
|
||||||
let cachedScrollbarWidth: number | null = null;
|
export const getScrollbarWidth = () => {
|
||||||
|
if (isMobile(window.innerWidth)) {
|
||||||
const getActualScrollbarWidth = () => {
|
return 0;
|
||||||
|
}
|
||||||
const outer = document.createElement('div');
|
const outer = document.createElement('div');
|
||||||
outer.style.visibility = 'hidden';
|
outer.style.visibility = 'hidden';
|
||||||
outer.style.overflow = 'scroll';
|
outer.style.overflow = 'scroll';
|
||||||
|
@ -16,16 +17,3 @@ const getActualScrollbarWidth = () => {
|
||||||
|
|
||||||
return scrollbarWidth;
|
return scrollbarWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getScrollbarWidth = () => {
|
|
||||||
if (cachedScrollbarWidth !== null) {
|
|
||||||
return cachedScrollbarWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollbarWidth = isMobile(window.innerWidth)
|
|
||||||
? 0
|
|
||||||
: getActualScrollbarWidth();
|
|
||||||
cachedScrollbarWidth = scrollbarWidth;
|
|
||||||
|
|
||||||
return scrollbarWidth;
|
|
||||||
};
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ body {
|
||||||
&.with-modals--active {
|
&.with-modals--active {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
margin-right: var(--root-scrollbar-width, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2894,6 +2894,11 @@ a.account__display-name {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
backdrop-filter: var(--background-filter);
|
backdrop-filter: var(--background-filter);
|
||||||
border-top: 1px solid var(--background-border-color);
|
border-top: 1px solid var(--background-border-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.with-modals--active & {
|
||||||
|
padding-right: var(--root-scrollbar-width);
|
||||||
|
}
|
||||||
|
|
||||||
.layout-multiple-columns & {
|
.layout-multiple-columns & {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user