mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-30 22:34:01 -05:00
Fixed layout shift issue when Portal popup appears (#21895)
ref DES-547 - when Portal popup is opened and the browser scroll bar is visible, it used to make layout shift, because we were hiding the scrollbar - now it applies right margin to body element and the trigger button by calculating the scrollbar width only when the browser scroll bar is visible - it also preservers the current right margin for those elements and makes the calculation based on that
This commit is contained in:
parent
3d65bfa38d
commit
4bc85e2ff2
2 changed files with 56 additions and 5 deletions
|
@ -57,11 +57,15 @@ export default class App extends React.Component {
|
|||
initStatus: 'running',
|
||||
lastPage: null,
|
||||
customSiteUrl: props.customSiteUrl,
|
||||
locale: props.locale
|
||||
locale: props.locale,
|
||||
scrollbarWidth: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const scrollbarWidth = this.getScrollbarWidth();
|
||||
this.setState({scrollbarWidth});
|
||||
|
||||
this.initSetup();
|
||||
}
|
||||
|
||||
|
@ -75,10 +79,19 @@ export default class App extends React.Component {
|
|||
if (this.state.showPopup) {
|
||||
/** When modal is opened, store current overflow and set as hidden */
|
||||
this.bodyScroll = window.document?.body?.style?.overflow;
|
||||
this.bodyMargin = window.getComputedStyle(document.body).getPropertyValue('margin-right');
|
||||
window.document.body.style.overflow = 'hidden';
|
||||
if (this.state.scrollbarWidth) {
|
||||
window.document.body.style.marginRight = `calc(${this.bodyMargin} + ${this.state.scrollbarWidth}px)`;
|
||||
}
|
||||
} else {
|
||||
/** When the modal is hidden, reset overflow property for body */
|
||||
window.document.body.style.overflow = this.bodyScroll || '';
|
||||
if (!this.bodyMargin || this.bodyMargin === '0px') {
|
||||
window.document.body.style.marginRight = '';
|
||||
} else {
|
||||
window.document.body.style.marginRight = this.bodyMargin;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
/** Ignore any errors for scroll handling */
|
||||
|
@ -115,6 +128,27 @@ export default class App extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// User for adding trailing margin to prevent layout shift when popup appears
|
||||
getScrollbarWidth() {
|
||||
// Create a temporary div
|
||||
const div = document.createElement('div');
|
||||
div.style.visibility = 'hidden';
|
||||
div.style.overflow = 'scroll'; // forcing scrollbar to appear
|
||||
document.body.appendChild(div);
|
||||
|
||||
// Create an inner div
|
||||
// const inner = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
|
||||
// Calculate the width difference
|
||||
const scrollbarWidth = div.offsetWidth - div.clientWidth;
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(div);
|
||||
|
||||
return scrollbarWidth;
|
||||
}
|
||||
|
||||
/** Setup custom trigger buttons handling on page */
|
||||
setupCustomTriggerButton() {
|
||||
// Handler for custom buttons
|
||||
|
@ -161,7 +195,7 @@ export default class App extends React.Component {
|
|||
const {site, member, page, showPopup, popupNotification, lastPage, pageQuery, pageData} = await this.fetchData();
|
||||
const i18nLanguage = this.props.siteI18nEnabled ? this.props.locale || site.locale || 'en' : 'en';
|
||||
const i18n = i18nLib(i18nLanguage, 'portal');
|
||||
|
||||
|
||||
const state = {
|
||||
site,
|
||||
member,
|
||||
|
@ -926,7 +960,7 @@ export default class App extends React.Component {
|
|||
|
||||
/**Get final App level context from App state*/
|
||||
getContextFromState() {
|
||||
const {site, member, action, page, lastPage, showPopup, pageQuery, pageData, popupNotification, customSiteUrl, t, dir} = this.state;
|
||||
const {site, member, action, page, lastPage, showPopup, pageQuery, pageData, popupNotification, customSiteUrl, t, dir, scrollbarWidth} = this.state;
|
||||
const contextPage = this.getContextPage({site, page, member});
|
||||
const contextMember = this.getContextMember({page: contextPage, member, customSiteUrl});
|
||||
return {
|
||||
|
@ -944,6 +978,7 @@ export default class App extends React.Component {
|
|||
customSiteUrl,
|
||||
t,
|
||||
dir,
|
||||
scrollbarWidth,
|
||||
onAction: (_action, data) => this.dispatchAction(_action, data)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -223,6 +223,18 @@ export default class TriggerButton extends React.Component {
|
|||
this.state = {
|
||||
width: null
|
||||
};
|
||||
this.buttonRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
if (this.buttonRef.current) {
|
||||
const iframeElement = this.buttonRef.current.node;
|
||||
if (iframeElement) {
|
||||
this.buttonMargin = window.getComputedStyle(iframeElement).getPropertyValue('margin-right');
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
onWidthChange(width) {
|
||||
|
@ -251,7 +263,7 @@ export default class TriggerButton extends React.Component {
|
|||
render() {
|
||||
const site = this.context.site;
|
||||
const {portal_button: portalButton} = site;
|
||||
const {showPopup} = this.context;
|
||||
const {showPopup, scrollbarWidth} = this.context;
|
||||
|
||||
if (!portalButton || !isSigninAllowed({site}) || hasMode(['offerPreview'])) {
|
||||
return null;
|
||||
|
@ -268,8 +280,12 @@ export default class TriggerButton extends React.Component {
|
|||
frameStyle.width = `${updatedWidth}px`;
|
||||
}
|
||||
|
||||
if (scrollbarWidth && showPopup) {
|
||||
frameStyle.marginRight = `calc(${scrollbarWidth}px + ${this.buttonMargin})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Frame dataTestId='portal-trigger-frame' className='gh-portal-triggerbtn-iframe' style={frameStyle} title="portal-trigger" head={this.renderFrameStyles()}>
|
||||
<Frame ref={this.buttonRef} dataTestId='portal-trigger-frame' className='gh-portal-triggerbtn-iframe' style={frameStyle} title="portal-trigger" head={this.renderFrameStyles()}>
|
||||
<TriggerButtonContent isPopupOpen={showPopup} updateWidth={width => this.onWidthChange(width)} />
|
||||
</Frame>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue