mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { Injectable, Inject } from '@angular/core';
|
|
import { PlatformLocation } from '@angular/common';
|
|
import { DOCUMENT } from '@angular/platform-browser';
|
|
import { fromEvent } from 'rxjs';
|
|
|
|
export const topMargin = 16;
|
|
/**
|
|
* A service that scrolls document elements into view
|
|
*/
|
|
@Injectable()
|
|
export class ScrollService {
|
|
|
|
private _topOffset: number | null;
|
|
private _topOfPageElement: Element;
|
|
|
|
// Offset from the top of the document to bottom of any static elements
|
|
// at the top (e.g. toolbar) + some margin
|
|
get topOffset() {
|
|
if (!this._topOffset) {
|
|
const toolbar = this.document.querySelector('.app-toolbar');
|
|
this._topOffset = (toolbar && toolbar.clientHeight || 0) + 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|null) {
|
|
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 decodeURIComponent(this.location.hash.replace(/^#/, ''));
|
|
}
|
|
}
|