forked from angular/components
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(overlay): add a utility for disabling body scroll
Adds a `DisableBodyScroll` injectable that can toggle whether the body is scrollable. Fixes angular#1662.
- Loading branch information
Showing
4 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import {DisableBodyScroll} from './disable-body-scroll'; | ||
|
||
|
||
describe('DisableBodyScroll', () => { | ||
let service: DisableBodyScroll; | ||
let forceScrollElement: HTMLElement; | ||
|
||
beforeEach(() => { | ||
forceScrollElement = document.createElement('div'); | ||
forceScrollElement.style.height = '3000px'; | ||
document.body.appendChild(forceScrollElement); | ||
service = new DisableBodyScroll(); | ||
}); | ||
|
||
afterEach(() => { | ||
forceScrollElement.parentNode.removeChild(forceScrollElement); | ||
forceScrollElement = null; | ||
service.deactivate(); | ||
}); | ||
|
||
it('should prevent scrolling', () => { | ||
window.scroll(0, 0); | ||
|
||
service.activate(); | ||
|
||
window.scroll(0, 500); | ||
|
||
expect(window.pageYOffset).toBe(0); | ||
}); | ||
|
||
it('should toggle the isActive property', () => { | ||
service.activate(); | ||
expect(service.isActive).toBe(true); | ||
|
||
service.deactivate(); | ||
expect(service.isActive).toBe(false); | ||
}); | ||
|
||
it('should not disable scrolling if the content is shorter than the viewport height', () => { | ||
forceScrollElement.style.height = '0'; | ||
service.activate(); | ||
expect(service.isActive).toBe(false); | ||
}); | ||
|
||
it('should add the proper inline styles to the <body> and <html> nodes', () => { | ||
let bodyCSS = document.body.style; | ||
let htmlCSS = document.documentElement.style; | ||
|
||
window.scroll(0, 500); | ||
service.activate(); | ||
|
||
expect(bodyCSS.position).toBe('fixed'); | ||
expect(bodyCSS.width).toBe('100%'); | ||
expect(bodyCSS.top).toBe('-500px'); | ||
expect(bodyCSS.maxWidth).toBeTruthy(); | ||
expect(htmlCSS.overflowY).toBe('scroll'); | ||
}); | ||
|
||
it('should revert any previously-set inline styles', () => { | ||
let bodyCSS = document.body.style; | ||
let htmlCSS = document.documentElement.style; | ||
|
||
bodyCSS.position = 'static'; | ||
bodyCSS.width = '1000px'; | ||
htmlCSS.overflowY = 'hidden'; | ||
|
||
service.activate(); | ||
service.deactivate(); | ||
|
||
expect(bodyCSS.position).toBe('static'); | ||
expect(bodyCSS.width).toBe('1000px'); | ||
expect(htmlCSS.overflowY).toBe('hidden'); | ||
|
||
bodyCSS.cssText = ''; | ||
htmlCSS.cssText = ''; | ||
}); | ||
|
||
it('should restore the scroll position when enabling scrolling', () => { | ||
window.scroll(0, 1000); | ||
|
||
service.activate(); | ||
service.deactivate(); | ||
|
||
expect(window.pageYOffset).toBe(1000); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import {Injectable} from '@angular/core'; | ||
|
||
/** | ||
* Utilitity that allows for toggling scrolling of the viewport on/off. | ||
*/ | ||
@Injectable() | ||
export class DisableBodyScroll { | ||
private _bodyStyles: string = ''; | ||
private _htmlStyles: string = ''; | ||
private _previousScrollPosition: number = 0; | ||
private _isActive: boolean = false; | ||
|
||
/** Whether scrolling is disabled. */ | ||
public get isActive(): boolean { | ||
return this._isActive; | ||
} | ||
|
||
/** | ||
* Disables scrolling if it hasn't been disabled already and if the body is scrollable. | ||
*/ | ||
activate(): void { | ||
if (!this.isActive && document.body.scrollHeight > window.innerHeight) { | ||
let body = document.body; | ||
let html = document.documentElement; | ||
let initialBodyWidth = body.clientWidth; | ||
|
||
this._htmlStyles = html.style.cssText || ''; | ||
this._bodyStyles = body.style.cssText || ''; | ||
this._previousScrollPosition = window.scrollY || window.pageYOffset || 0; | ||
|
||
body.style.position = 'fixed'; | ||
body.style.width = '100%'; | ||
body.style.top = -this._previousScrollPosition + 'px'; | ||
html.style.overflowY = 'scroll'; | ||
|
||
// TODO(crisbeto): this avoids issues if the body has a margin, however it prevents the | ||
// body from adapting if the window is resized. check whether it's ok to reset the body | ||
// margin in the core styles. | ||
body.style.maxWidth = initialBodyWidth + 'px'; | ||
|
||
this._isActive = true; | ||
} | ||
} | ||
|
||
/** | ||
* Re-enables scrolling. | ||
*/ | ||
deactivate(): void { | ||
if (this.isActive) { | ||
document.body.style.cssText = this._bodyStyles; | ||
document.documentElement.style.cssText = this._htmlStyles; | ||
window.scroll(0, this._previousScrollPosition); | ||
this._isActive = false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters