ページを下にスクロールしてページ下部のリンクをクリックすると、Angular 2アプリでルートが変更されて次のページに移動しますが、ページの上部にスクロールしません。ページ。その結果、1ページ目が長くて2ページ目の内容が少ない場合は、2ページ目の内容が不足しているように見えます。ユーザーがページの一番上までスクロールした場合にのみコンテンツが表示されるので。
コンポーネントのngInitでウィンドウをページの一番上までスクロールできますが、アプリ内のすべてのルートを自動的に処理できるより良い解決策はありますか?
メインコンポーネントにルート変更リスナーを登録し、ルート変更時に一番上にスクロールすることができます。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) {
return;
}
window.scrollTo(0, 0)
});
}
}
Angular 6.1以降 :
Angular 6.1(2018-07-25にリリース)は、 "Router Scroll Position Restoration"と呼ばれる機能を通して、この問題を処理するための組み込みサポートを追加しました。公式の Angularブログ で説明されているように、ルータ設定でこれを有効にする必要があります。
RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})
さらに、ブログは「これは将来のメジャーリリースでデフォルトになると予想される」と述べています。これまでのところ(Angular 7.xの時点では)これは発生していませんが、最終的にはコード内で何もする必要はなくなり、そのまま使用できます。
Angular 6.0以前 :
@ GuilhermeMeirelesの優れた答えは元の問題を解決しますが、前後に移動したときに予想される通常の動作を中断することによって(ブラウザボタンまたはコード内のLocationを使用して)新しい問題をもたらします。予想される動作は、ページに戻ってもリンクをクリックしたときと同じ場所までスクロールしたままにしておく必要があることですが、すべてのページに到達したときに上にスクロールするとこの予想は明らかに崩れます。
以下のコードは、LocationのPopStateEventシーケンスをサブスクライブし、新しく到着したページがそのようなイベントの結果である場合は上にスクロールするロジックをスキップすることによって、この種のナビゲーションを検出するロジックを拡張します。
戻るページがビューポート全体をカバーするのに十分な長さである場合は、スクロール位置は自動的に復元されますが、@ JordanNelsonが正しく指摘したように、ページが短い場合は元のyスクロール位置を追跡して復元する必要があります。ページに戻ったときに明示的にコードの更新版では、スクロール位置を常に明示的に復元することによって、この場合もカバーしています。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
private lastPoppedUrl: string;
private yScrollStack: number[] = [];
constructor(private router: Router, private location: Location) { }
ngOnInit() {
this.location.subscribe((ev:PopStateEvent) => {
this.lastPoppedUrl = ev.url;
});
this.router.events.subscribe((ev:any) => {
if (ev instanceof NavigationStart) {
if (ev.url != this.lastPoppedUrl)
this.yScrollStack.Push(window.scrollY);
} else if (ev instanceof NavigationEnd) {
if (ev.url == this.lastPoppedUrl) {
this.lastPoppedUrl = undefined;
window.scrollTo(0, this.yScrollStack.pop());
} else
window.scrollTo(0, 0);
}
});
}
}
Angular 6.1から、煩雑さを避けてextraOptions
を2番目のパラメータとしてRouterModule.forRoot()
に渡すことができ、scrollPositionRestoration: enabled
を指定してAngularにルートが変わるたびに上にスクロールするように指示できます。
デフォルトでは、これはapp-routing.module.ts
にあります。
const routes: Routes = [
{
path: '...'
component: ...
},
...
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled', // Add options right here
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
観察可能なfilter
メソッドを利用することで、これをもっと簡潔に書くことができます。
this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
this.window.scrollTo(0, 0);
});
Angular Material 2サイドナビを使用しているときに上部にスクロールする問題がある場合は、これが役立ちます。ウィンドウやドキュメント本体にはスクロールバーがないので、sidenav
コンテンツコンテナを取得してその要素をスクロールする必要があります。それ以外の場合は、デフォルトでウィンドウをスクロールしてみてください。
this.router.events.filter(event => event instanceof NavigationEnd)
.subscribe(() => {
const contentContainer = document.querySelector('.mat-sidenav-content') || this.window;
contentContainer.scrollTo(0, 0);
});
また、Angular CDK v6.xには スクローリングパッケージ があります。これはスクロールの処理に役立ちます。
サーバーサイドのレンダリングがある場合は、その変数が存在しないサーバーでwindows
を使用してコードを実行しないように注意する必要があります。コードが壊れる可能性があります。
export class AppComponent implements OnInit {
routerSubscription: Subscription;
constructor(private router: Router,
@Inject(PLATFORM_ID) private platformId: any) {}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
this.routerSubscription = this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(event => {
window.scrollTo(0, 0);
});
}
}
ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
}
isPlatformBrowser
は、アプリケーションがレンダリングされている現在のプラットフォームがブラウザかどうかを確認するために使用される関数です。注入されたplatformId
を渡します。
安全のために、変数windows
の存在をチェックすることも可能です。
if (typeof window != 'undefined')
クリック操作で簡単に行えます
あなたのメインコンポーネントのhtmlに#scrollContainerを参照してください
<div class="main-container" #scrollContainer>
<router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
</div>
主成分の.tsに
onActivate(e, scrollContainer) {
scrollContainer.scrollTop = 0;
}
最良の答えはAngular GitHubのディスカッションにあります( 新しいページでルートを変更しても一番上にスクロールしません )。
たぶんあなたはルートルーターの変更だけでトップに行きたい(あなたはf.e.タブセットで遅延ロードでルートをロードすることができるので子ではなく)
app.component.html
<router-outlet (deactivate)="onDeactivate()"></router-outlet>
app.component.ts
onDeactivate() {
document.body.scrollTop = 0;
// Alternatively, you can scroll to top by using this other call:
// window.scrollTo(0, 0)
}
あなたのコンポーネントに AfterViewInit lifecycleフックを追加することができます。
ngAfterViewInit() {
window.scrollTo(0, 0);
}
これが私が思いついた解決策です。 LocationStrategyとRouterイベントを組み合わせました。 LocationStrategyを使用して、ユーザーがブラウザの履歴を現在移動している時期を知るためのブール値を設定します。このように、私はたくさんのURLとyスクロールデータを保存する必要はありません(それぞれのデータはURLに基づいて置き換えられるので、いずれにしてもうまくいきません)。これは、ユーザーがブラウザの戻るボタンまたは進むボタンを押したままにして、1ページだけではなく複数のページを前後に移動することにした場合のEdgeの問題も解決します。
P.S私はIE、Chrome、FireFox、Safari、そしてOperaの最新版でのみテストした(この記事の時点で)。
お役に立てれば。
export class AppComponent implements OnInit {
isPopState = false;
constructor(private router: Router, private locStrat: LocationStrategy) { }
ngOnInit(): void {
this.locStrat.onPopState(() => {
this.isPopState = true;
});
this.router.events.subscribe(event => {
// Scroll to top if accessing a page, not via browser history stack
if (event instanceof NavigationEnd && !this.isPopState) {
window.scrollTo(0, 0);
this.isPopState = false;
}
// Ensures that isPopState is reset
if (event instanceof NavigationEnd) {
this.isPopState = false;
}
});
}
}
Angular 6.1以降、ルーターはscrollPositionRestoration
と呼ばれる 設定オプション を提供します。これはこのシナリオに対応するように設計されています。
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
}),
...
]
単にページを上にスクロールする必要がある場合は、これを実行できます(最善の解決策ではありませんが高速です)。
document.getElementById('elementId').scrollTop = 0;
このソリューションは@ FernandoEcheverriaと@ GuilhermeMeirelesのソリューションに基づいていますが、より簡潔でAngularルーターが提供するpopstateメカニズムと連携します。これにより、複数の連続したナビゲーションのスクロールレベルを保存および復元できます。
各ナビゲーション状態のスクロール位置をマップscrollLevels
に格納します。 popstateイベントが発生すると、復元されようとしている状態のIDがAngular Router:event.restoredState.navigationId
によって提供されます。これはその後、その状態の最後のスクロールレベルをscrollLevels
から取得するために使用されます。
ルートに格納されているスクロールレベルがない場合は、予想どおりに一番上にスクロールします。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class AppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
const scrollLevels: { [navigationId: number]: number } = {};
let lastId = 0;
let restoredId: number;
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
scrollLevels[lastId] = window.scrollY;
lastId = event.id;
restoredId = event.restoredState ? event.restoredState.navigationId : undefined;
}
if (event instanceof NavigationEnd) {
if (restoredId) {
// Optional: Wrap a timeout around the next line to wait for
// the component to finish loading
window.scrollTo(0, scrollLevels[restoredId] || 0);
} else {
window.scrollTo(0, 0);
}
}
});
}
}
Angularは最近angularルーティングモジュール内で以下のような変更を行う新しい機能を導入しました
@NgModule({
imports: [RouterModule.forRoot(routes,{
scrollPositionRestoration: 'top'
})],
iphone/iosサファリのためにあなたはsetTimeoutで包むことができます
setTimeout(function(){
window.scrollTo(0, 1);
}, 0);
こんにちはみんなこれは角度4で私のために動作しますあなたはルータの変更をスクロールするために親を参照する必要があります
.wrapper(#outlet="")
router-outlet((activate)='routerActivate($event,outlet)')
public routerActivate(event,outlet){
outlet.scrollTop = 0;
}`
Router
自体を使用すると、一貫したブラウザエクスペリエンスを維持するために完全には克服できない問題が発生します。私の意見では、最善の方法はカスタムのdirective
を使うことで、クリック時にスクロールをリセットすることです。これについての良いところは、あなたがクリックしたのと同じurl
上にいる場合、ページも上にスクロールバックするということです。これは通常のWebサイトと一致しています。基本的なdirective
は次のようになります。
import {Directive, HostListener} from '@angular/core';
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective {
@HostListener('click')
onClick(): void {
window.scrollTo(0, 0);
}
}
次のように使用します。
<a routerLink="/" linkToTop></a>
ほとんどのユースケースではこれで十分ですが、これから発生する可能性があるいくつかの問題を想像できます。
universal
を使用しているため、window
では動作しません実際にこれらの問題を克服することはかなり簡単です:
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective implements OnInit, OnDestroy {
@Input()
set linkToTop(active: string | boolean) {
this.active = typeof active === 'string' ? active.length === 0 : active;
}
private active: boolean = true;
private onClick: EventListener = (event: MouseEvent) => {
if (this.active) {
window.scrollTo(0, 0);
}
};
constructor(@Inject(PLATFORM_ID) private readonly platformId: Object,
private readonly elementRef: ElementRef,
private readonly ngZone: NgZone
) {}
ngOnDestroy(): void {
if (isPlatformBrowser(this.platformId)) {
this.elementRef.nativeElement.removeEventListener('click', this.onClick, false);
}
}
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
this.ngZone.runOutsideAngular(() =>
this.elementRef.nativeElement.addEventListener('click', this.onClick, false)
);
}
}
}
これは基本的な使い方と同じ使い方で、ほとんどのユースケースを考慮に入れています。
<a routerLink="/" linkToTop></a> <!-- always active -->
<a routerLink="/" [linkToTop]="isActive"> <!-- active when `isActive` is true -->
コマーシャル、宣伝したくない場合は読んではいけない
ブラウザがpassive
イベントをサポートしているかどうかを確認するために、もう1つの改善が行われる可能性があります。これはコードをもう少し複雑にするでしょう、そしてあなたがあなたのカスタムディレクティブ/テンプレートでこれらすべてを実装したいならば、少しあいまいです。だから私はあなたがこれらの問題に取り組むのに使える小さな library を書いたのです。 ng-event-options
ライブラリを使用する場合は、上記と同じ機能を持ち、追加されたpassive
イベントを使用して、ディレクティブをこれに変更できます。ロジックはclick.pnb
リスナーの内部にあります。
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective {
@Input()
set linkToTop(active: string|boolean) {
this.active = typeof active === 'string' ? active.length === 0 : active;
}
private active: boolean = true;
@HostListener('click.pnb')
onClick(): void {
if (this.active) {
window.scrollTo(0, 0);
}
}
}
以下に示すように @Guilherme Meireles によって提供される完璧な答えに加えて、以下に示すようにスムーズスクロールを追加することで実装を微調整できます。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) {
return;
}
window.scrollTo(0, 0)
});
}
}
それから下にスニペットを追加します
html {
scroll-behavior: smooth;
}
あなたのstyles.cssに
このコードの背後にある主な考え方は、すべてのアクセスされたURLをそれぞれのscrollYデータとともに配列に保持することです。ユーザーがページ(NavigationStart)を放棄するたびに、この配列は更新されます。ユーザーが新しいページに移動するたびに(NavigationEnd)、Y位置を元に戻すか、このページへのアクセス方法に依存しないかを決定します。あるページの参照が使用されている場合は、0にスクロールします。ブラウザのバック/フォワード機能が使用されている場合は、配列に保存されているYにスクロールします。私の英語ですみません:)
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Location, PopStateEvent } from '@angular/common';
import { Router, Route, RouterLink, NavigationStart, NavigationEnd,
RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'my-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
private _subscription: Subscription;
private _scrollHistory: { url: string, y: number }[] = [];
private _useHistory = false;
constructor(
private _router: Router,
private _location: Location) {
}
public ngOnInit() {
this._subscription = this._router.events.subscribe((event: any) =>
{
if (event instanceof NavigationStart) {
const currentUrl = (this._location.path() !== '')
this._location.path() : '/';
const item = this._scrollHistory.find(x => x.url === currentUrl);
if (item) {
item.y = window.scrollY;
} else {
this._scrollHistory.Push({ url: currentUrl, y: window.scrollY });
}
return;
}
if (event instanceof NavigationEnd) {
if (this._useHistory) {
this._useHistory = false;
window.scrollTo(0, this._scrollHistory.find(x => x.url ===
event.url).y);
} else {
window.scrollTo(0, 0);
}
}
});
this._subscription.add(this._location.subscribe((event: PopStateEvent)
=> { this._useHistory = true;
}));
}
public ngOnDestroy(): void {
this._subscription.unsubscribe();
}
}
window.scrollTo()
はAngular 5では私のところでは動作しないので、私はdocument.body.scrollTop
のように使いました、
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
document.body.scrollTop = 0;
}
});
これは私にとってハッシュナビゲーションを含むすべてのナビゲーションの変更に最適でした。
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this._sub = this.route.fragment.subscribe((hash: string) => {
if (hash) {
const cmp = document.getElementById(hash);
if (cmp) {
cmp.scrollIntoView();
}
} else {
window.scrollTo(0, 0);
}
});
}
@ Fernando Echeverriaすごい!しかし、このコードはハッシュルータやレイジールータでは動作しません。それらは位置の変更を引き起こさないからです。これを試すことができます:
private lastRouteUrl: string[] = []
ngOnInit(): void {
this.router.events.subscribe((ev) => {
const len = this.lastRouteUrl.length
if (ev instanceof NavigationEnd) {
this.lastRouteUrl.Push(ev.url)
if (len > 1 && ev.url === this.lastRouteUrl[len - 2]) {
return
}
window.scrollTo(0, 0)
}
})
}