/
scrollbar.ts
80 lines (66 loc) · 2.54 KB
/
scrollbar.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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;
/**
* 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.
*/
@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
*
* @return a callback used to revert the compensation (noop if there was none,
* otherwise a function removing the padding)
*/
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 {
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;
}
}