3 * Copyright Google Inc. All Rights Reserved.
5 * Use of this source code is governed by an MIT-style license that can be
6 * found in the LICENSE file at https://angular.io/license
8 /* tslint:disable:array-type member-access variable-name typedef
9 only-arrow-functions directive-class-suffix component-class-suffix
10 component-selector no-unnecessary-type-assertion arrow-parens*/
11 import {Injectable, Optional, SkipSelf} from '@angular/core';
12 import {ScrollDispatcher} from '../scroll/scroll-dispatcher';
16 * Simple utility for getting the bounds of the browser viewport.
20 export class ViewportRuler {
21 /** Cached document client rectangle. */
22 private _documentRect?: ClientRect;
24 constructor(scrollDispatcher: ScrollDispatcher) {
25 // Subscribe to scroll and resize events and update the document rectangle
27 scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry());
30 /** Gets a ClientRect for the viewport's bounds. */
31 getViewportRect(documentRect = this._documentRect): ClientRect {
32 // Cache the document bounding rect so that we don't recompute it for
35 this._cacheViewportGeometry();
36 documentRect = this._documentRect;
39 // Use the document element's bounding rect rather than the window scroll
40 // properties (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE
41 // where window scroll properties and client coordinates
42 // (boundingClientRect, clientX/Y, etc.) are in different conceptual
43 // viewports. Under most circumstances these viewports are equivalent, but
44 // they can disagree when the page is pinch-zoomed (on devices that support
46 // https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4 We use
47 // the documentElement instead of the body because, by default (without a
48 // css reset) browsers typically give the document body an 8px margin, which
49 // is not included in getBoundingClientRect().
50 const scrollPosition = this.getViewportScrollPosition(documentRect);
51 const height = window.innerHeight;
52 const width = window.innerWidth;
55 top: scrollPosition.top,
56 left: scrollPosition.left,
57 bottom: scrollPosition.top + height,
58 right: scrollPosition.left + width,
66 * Gets the (top, left) scroll position of the viewport.
69 getViewportScrollPosition(documentRect = this._documentRect) {
70 // Cache the document bounding rect so that we don't recompute it for
73 this._cacheViewportGeometry();
74 documentRect = this._documentRect;
77 // The top-left-corner of the viewport is determined by the scroll position
78 // of the document body, normally just (scrollLeft, scrollTop). However,
79 // Chrome and Firefox disagree about whether `document.body` or
80 // `document.documentElement` is the scrolled element, so reading
81 // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding
82 // rect of `document.documentElement` works consistently, where the `top`
83 // and `left` values will equal negative the scroll position.
84 const top = -documentRect.top || document.body.scrollTop ||
85 window.scrollY || document.documentElement.scrollTop || 0;
87 const left = -documentRect.left || document.body.scrollLeft ||
88 window.scrollX || document.documentElement.scrollLeft || 0;
93 /** Caches the latest client rectangle of the document element. */
94 _cacheViewportGeometry() {
95 this._documentRect = document.documentElement.getBoundingClientRect();
99 export function VIEWPORT_RULER_PROVIDER_FACTORY(
100 parentRuler: ViewportRuler, scrollDispatcher: ScrollDispatcher) {
101 return parentRuler || new ViewportRuler(scrollDispatcher);
104 export const VIEWPORT_RULER_PROVIDER = {
105 // If there is already a ViewportRuler available, use that. Otherwise, provide
107 provide: ViewportRuler,
108 deps: [[new Optional(), new SkipSelf(), ViewportRuler], ScrollDispatcher],
109 useFactory: VIEWPORT_RULER_PROVIDER_FACTORY