11import { Injectable , Optional , SkipSelf } from '@angular/core' ;
2+ import { ScrollDispatcher } from '../scroll/scroll-dispatcher' ;
23
34
45/**
@@ -7,12 +8,20 @@ import {Injectable, Optional, SkipSelf} from '@angular/core';
78 */
89@Injectable ( )
910export class ViewportRuler {
10- // TODO(jelbourn): cache the document's bounding rect and only update it when the window
11- // is resized (debounced).
1211
12+ /** Cached document client rectangle. */
13+ private _documentRect ?: ClientRect ;
14+
15+ constructor ( scrollDispatcher : ScrollDispatcher ) {
16+ // Initially cache the document rectangle.
17+ this . _cacheViewportGeometry ( ) ;
18+
19+ // Subscribe to scroll and resize events and update the document rectangle on changes.
20+ scrollDispatcher . scrolled ( ) . subscribe ( ( ) => this . _cacheViewportGeometry ( ) ) ;
21+ }
1322
1423 /** Gets a ClientRect for the viewport's bounds. */
15- getViewportRect ( ) : ClientRect {
24+ getViewportRect ( documentRect = this . _documentRect ) : ClientRect {
1625 // Use the document element's bounding rect rather than the window scroll properties
1726 // (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
1827 // properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
@@ -22,7 +31,6 @@ export class ViewportRuler {
2231 // We use the documentElement instead of the body because, by default (without a css reset)
2332 // browsers typically give the document body an 8px margin, which is not included in
2433 // getBoundingClientRect().
25- const documentRect = document . documentElement . getBoundingClientRect ( ) ;
2634 const scrollPosition = this . getViewportScrollPosition ( documentRect ) ;
2735 const height = window . innerHeight ;
2836 const width = window . innerWidth ;
@@ -42,7 +50,7 @@ export class ViewportRuler {
4250 * Gets the (top, left) scroll position of the viewport.
4351 * @param documentRect
4452 */
45- getViewportScrollPosition ( documentRect = document . documentElement . getBoundingClientRect ( ) ) {
53+ getViewportScrollPosition ( documentRect = this . _documentRect ) {
4654 // The top-left-corner of the viewport is determined by the scroll position of the document
4755 // body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
4856 // whether `document.body` or `document.documentElement` is the scrolled element, so reading
@@ -54,15 +62,22 @@ export class ViewportRuler {
5462
5563 return { top, left} ;
5664 }
65+
66+ /** Caches the latest client rectangle of the document element. */
67+ _cacheViewportGeometry ?( ) {
68+ this . _documentRect = document . documentElement . getBoundingClientRect ( ) ;
69+ }
70+
5771}
5872
59- export function VIEWPORT_RULER_PROVIDER_FACTORY ( parentDispatcher : ViewportRuler ) {
60- return parentDispatcher || new ViewportRuler ( ) ;
61- } ;
73+ export function VIEWPORT_RULER_PROVIDER_FACTORY ( parentRuler : ViewportRuler ,
74+ scrollDispatcher : ScrollDispatcher ) {
75+ return parentRuler || new ViewportRuler ( scrollDispatcher ) ;
76+ }
6277
6378export const VIEWPORT_RULER_PROVIDER = {
6479 // If there is already a ViewportRuler available, use that. Otherwise, provide a new one.
6580 provide : ViewportRuler ,
66- deps : [ [ new Optional ( ) , new SkipSelf ( ) , ViewportRuler ] ] ,
81+ deps : [ [ new Optional ( ) , new SkipSelf ( ) , ViewportRuler ] , ScrollDispatcher ] ,
6782 useFactory : VIEWPORT_RULER_PROVIDER_FACTORY
6883} ;
0 commit comments