src/app/articles/shared/scroll.service.ts
import { Injectable, Inject } from '@angular/core';
import { PlatformLocation } from '@angular/common';
import { DOCUMENT } from '@angular/platform-browser';
import { fromEvent } from 'rxjs';
export const topMargin = 12;
/**
* A service that scrolls document elements into view
*/
@Injectable()
export class ScrollService {
private _topOffset: number | null;
private _topOfPageElement: Element;
get topOffset() {
if (!this._topOffset) {
this._topOffset = topMargin;
}
return this._topOffset;
}
get topOfPageElement() {
if (!this._topOfPageElement) {
this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
}
return this._topOfPageElement;
}
constructor(
@Inject(DOCUMENT) private document: any,
private location: PlatformLocation) {
// On resize, the toolbar might change height, so "invalidate" the top offset.
fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
}
/**
* Scroll to the element with id extracted from the current location hash fragment.
* Scroll to top if no hash.
* Don't scroll if hash not found.
*/
scroll() {
const hash = this.getCurrentHash();
const element: HTMLElement = hash
? this.document.getElementById(hash)
: this.topOfPageElement;
this.scrollToElement(element);
}
/**
* Scroll to the element.
* Don't scroll if no element.
*/
scrollToElement(element: Element) {
if (element) {
element.scrollIntoView();
if (window && window.scrollBy) {
// Scroll as much as necessary to align the top of `element` at `topOffset`.
// (Usually, `.top` will be 0, except for cases where the element cannot be scrolled all the
// way to the top, because the viewport is larger than the height of the content after the
// element.)
window.scrollBy(0, element.getBoundingClientRect().top - this.topOffset);
// If we are very close to the top (<20px), then scroll all the way up.
// (This can happen if `element` is at the top of the page, but has a small top-margin.)
if (window.pageYOffset < 20) {
window.scrollBy(0, -window.pageYOffset);
}
}
}
}
/** Scroll to the top of the document. */
scrollToTop() {
this.scrollToElement(this.topOfPageElement);
}
/**
* Return the hash fragment from the `PlatformLocation`, minus the leading `#`.
*/
private getCurrentHash() {
return this.location.hash.replace(/^#/, '');
}
}