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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:array-type member-access variable-name typedef
only-arrow-functions directive-class-suffix component-class-suffix
component-selector no-unnecessary-type-assertion arrow-parens*/
import {Injectable, Optional, SkipSelf} from '@angular/core';
import {ScrollDispatcher} from '../scroll/scroll-dispatcher';
/**
* Simple utility for getting the bounds of the browser viewport.
* @docs-private
*/
@Injectable()
export class ViewportRuler {
/** Cached document client rectangle. */
private _documentRect?: ClientRect;
constructor(scrollDispatcher: ScrollDispatcher) {
// Subscribe to scroll and resize events and update the document rectangle
// on changes.
scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry());
}
/** Gets a ClientRect for the viewport's bounds. */
getViewportRect(documentRect = this._documentRect): ClientRect {
// Cache the document bounding rect so that we don't recompute it for
// multiple calls.
if (!documentRect) {
this._cacheViewportGeometry();
documentRect = this._documentRect;
}
// Use the document element's bounding rect rather than the window scroll
// properties (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE
// where window scroll properties and client coordinates
// (boundingClientRect, clientX/Y, etc.) are in different conceptual
// viewports. Under most circumstances these viewports are equivalent, but
// they can disagree when the page is pinch-zoomed (on devices that support
// touch). See
// https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4 We use
// the documentElement instead of the body because, by default (without a
// css reset) browsers typically give the document body an 8px margin, which
// is not included in getBoundingClientRect().
const scrollPosition = this.getViewportScrollPosition(documentRect);
const height = window.innerHeight;
const width = window.innerWidth;
return {
top: scrollPosition.top,
left: scrollPosition.left,
bottom: scrollPosition.top + height,
right: scrollPosition.left + width,
height,
width,
};
}
/**
* Gets the (top, left) scroll position of the viewport.
* @param documentRect
*/
getViewportScrollPosition(documentRect = this._documentRect) {
// Cache the document bounding rect so that we don't recompute it for
// multiple calls.
if (!documentRect) {
this._cacheViewportGeometry();
documentRect = this._documentRect;
}
// The top-left-corner of the viewport is determined by the scroll position
// of the document body, normally just (scrollLeft, scrollTop). However,
// Chrome and Firefox disagree about whether `document.body` or
// `document.documentElement` is the scrolled element, so reading
// `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding
// rect of `document.documentElement` works consistently, where the `top`
// and `left` values will equal negative the scroll position.
const top = -documentRect.top || document.body.scrollTop ||
window.scrollY || document.documentElement.scrollTop || 0;
const left = -documentRect.left || document.body.scrollLeft ||
window.scrollX || document.documentElement.scrollLeft || 0;
return {top, left};
}
/** Caches the latest client rectangle of the document element. */
_cacheViewportGeometry() {
this._documentRect = document.documentElement.getBoundingClientRect();
}
}
export function VIEWPORT_RULER_PROVIDER_FACTORY(
parentRuler: ViewportRuler, scrollDispatcher: ScrollDispatcher) {
return parentRuler || new ViewportRuler(scrollDispatcher);
}
export const VIEWPORT_RULER_PROVIDER = {
// If there is already a ViewportRuler available, use that. Otherwise, provide
// a new one.
provide: ViewportRuler,
deps: [[new Optional(), new SkipSelf(), ViewportRuler], ScrollDispatcher],
useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
};
|