Skip to content

Commit

Permalink
fix(modal): adds overflow: hidden from code and fixes scrollbar measu…
Browse files Browse the repository at this point in the history
…rement

fix #4128
  • Loading branch information
divdavem authored and fbasso committed Feb 8, 2022
1 parent f37fc9c commit 5c58309
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 64 deletions.
4 changes: 2 additions & 2 deletions src/modal/modal-stack.ts
Expand Up @@ -56,7 +56,7 @@ export class NgbModalStack {
this._document.body;
const renderer = this._rendererFactory.createRenderer(null, null);

const revertPaddingForScrollBar = this._scrollBar.compensate();
const revertScrollBar = this._scrollBar.hide();
const removeBodyClass = () => {
if (!this._modalRefs.length) {
renderer.removeClass(this._document.body, 'modal-open');
Expand All @@ -79,7 +79,7 @@ export class NgbModalStack {

this._registerModalRef(ngbModalRef);
this._registerWindowCmpt(windowCmptRef);
ngbModalRef.result.then(revertPaddingForScrollBar, revertPaddingForScrollBar);
ngbModalRef.result.then(revertScrollBar, revertScrollBar);
ngbModalRef.result.then(removeBodyClass, removeBodyClass);
activeModal.close = (result: any) => { ngbModalRef.close(result); };
activeModal.dismiss = (reason: any) => { ngbModalRef.dismiss(reason); };
Expand Down
86 changes: 24 additions & 62 deletions src/util/scrollbar.ts
@@ -1,80 +1,42 @@
import {Injectable, Inject} from '@angular/core';
import {DOCUMENT} from '@angular/common';


const noop = () => {};



/** Type for the callback used to revert the scrollbar compensation. */
export type CompensationReverter = () => void;


/** Type for the callback used to revert the scrollbar. */
export type ScrollbarReverter = () => void;

/**
* Utility to handle the scrollbar.
*
* It allows to compensate the lack of a vertical scrollbar by adding an
* equivalent padding on the right of the body, and to remove this compensation.
* It allows to hide the scrollbar and compensate the lack of a vertical scrollbar
* by adding an equivalent padding on the right of the body, and to revert this change.
*/
@Injectable({providedIn: 'root'})
export class ScrollBar {
constructor(@Inject(DOCUMENT) private _document: any) {}

/**
* To be called right before a potential vertical scrollbar would be removed:
*
* - if there was a scrollbar, adds some compensation padding to the body
* to keep the same layout as when the scrollbar is there
* - if there was none, there is nothing to do
* To be called to hide a potential vertical scrollbar:
* - if a scrollbar is there and has a width greater than 0, adds some compensation
* padding to the body to keep the same layout as when the scrollbar is there
* - adds overflow: hidden
*
* @return a callback used to revert the compensation (noop if there was none,
* otherwise a function removing the padding)
* @return a callback used to revert the change
*/
compensate(): CompensationReverter {
const width = this._getWidth();
return !this._isPresent(width) ? noop : this._adjustBody(width);
}

/**
* Adds a padding of the given width on the right of the body.
*
* @return a callback used to revert the padding to its previous value
*/
private _adjustBody(scrollbarWidth: number): CompensationReverter {
hide(): ScrollbarReverter {
const scrollbarWidth = Math.abs(window.innerWidth - this._document.documentElement.clientWidth);
const body = this._document.body;
const userSetPaddingStyle = body.style.paddingRight;
const actualPadding = parseFloat(window.getComputedStyle(body)['padding-right']);
body.style['padding-right'] = `${actualPadding + scrollbarWidth}px`;
return () => body.style['padding-right'] = userSetPaddingStyle;
}

/**
* Tells whether a scrollbar is currently present on the body.
*
* @return true if scrollbar is present, false otherwise
*/
private _isPresent(scrollbarWidth: number): boolean {
const rect = this._document.body.getBoundingClientRect();
const bodyToViewportGap = window.innerWidth - (rect.left + rect.right);
const uncertainty = 0.1 * scrollbarWidth;
return bodyToViewportGap >= scrollbarWidth - uncertainty;
}

/**
* Calculates and returns the width of a scrollbar.
*
* @return the width of a scrollbar on this page
*/
private _getWidth(): number {
const measurer = this._document.createElement('div');
measurer.className = 'modal-scrollbar-measure';

const body = this._document.body;
body.appendChild(measurer);
const width = measurer.getBoundingClientRect().width - measurer.clientWidth;
body.removeChild(measurer);

return width;
const bodyStyle = body.style;
const {overflow, paddingRight} = bodyStyle;
if (scrollbarWidth > 0) {
const actualPadding = parseFloat(window.getComputedStyle(body).paddingRight);
bodyStyle.paddingRight = `${actualPadding + scrollbarWidth}px`;
}
bodyStyle.overflow = 'hidden';
return () => {
if (scrollbarWidth > 0) {
bodyStyle.paddingRight = paddingRight;
}
bodyStyle.overflow = overflow;
};
}
}

0 comments on commit 5c58309

Please sign in to comment.