feat: always scroll to top on page switch

This commit is contained in:
2026-05-14 14:31:00 +02:00
parent 3bc31ec45f
commit 9069f4f6fc
2 changed files with 48 additions and 3 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<div class="app-content" [attr.aria-hidden]="isLanguageSelectorOpen() ? 'true' : null" [attr.inert]="isLanguageSelectorOpen() ? '' : null"> <div class="app-content" [attr.aria-hidden]="isLanguageSelectorOpen() ? 'true' : null" [attr.inert]="isLanguageSelectorOpen() ? '' : null">
<router-outlet></router-outlet> <router-outlet (activate)="scrollToTopAfterOutletActivation()"></router-outlet>
</div> </div>
<button <button
+47 -2
View File
@@ -1,14 +1,18 @@
import { DOCUMENT } from '@angular/common'; import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
DestroyRef,
computed, computed,
effect, effect,
inject, inject,
PLATFORM_ID,
signal, signal,
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import type { OnDestroy, OnInit } from '@angular/core'; import type { OnDestroy, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
import { filter } from 'rxjs';
import type { LanguageCode } from '../languages/language.types'; import type { LanguageCode } from '../languages/language.types';
import type { LanguageOption } from './app.types'; import type { LanguageOption } from './app.types';
@@ -23,7 +27,10 @@ import { LanguageService } from './services/language.service';
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
private readonly document = inject(DOCUMENT); private readonly document = inject(DOCUMENT);
private readonly destroyRef = inject(DestroyRef);
private readonly languageService = inject(LanguageService); private readonly languageService = inject(LanguageService);
private readonly platformId = inject(PLATFORM_ID);
private readonly router = inject(Router);
protected readonly content = this.languageService.shellContent; protected readonly content = this.languageService.shellContent;
protected readonly currentLanguageCode = this.languageService.languageCode; protected readonly currentLanguageCode = this.languageService.languageCode;
@@ -47,6 +54,13 @@ export class AppComponent implements OnInit, OnDestroy {
this.document.body.style.overflow = overflowValue; this.document.body.style.overflow = overflowValue;
this.document.documentElement.style.overflow = overflowValue; this.document.documentElement.style.overflow = overflowValue;
}); });
this.router.events
.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((event: NavigationEnd): void => this.scrollToTopAfterRouteChange(event));
} }
ngOnInit(): void { ngOnInit(): void {
@@ -84,6 +98,37 @@ export class AppComponent implements OnInit, OnDestroy {
return language === 'de' ? content.app.languageGerman : content.app.languageEnglish; return language === 'de' ? content.app.languageGerman : content.app.languageEnglish;
} }
protected scrollToTopAfterOutletActivation(): void {
if (!isPlatformBrowser(this.platformId)) {
return;
}
this.scrollDocumentToTop();
this.document.defaultView?.requestAnimationFrame((): void => this.scrollDocumentToTop());
this.document.defaultView?.setTimeout((): void => this.scrollDocumentToTop(), 0);
this.document.defaultView?.setTimeout((): void => this.scrollDocumentToTop(), 100);
}
private scrollToTopAfterRouteChange(event: NavigationEnd): void {
if (!isPlatformBrowser(this.platformId) || event.urlAfterRedirects.includes('#')) {
return;
}
this.scrollToTopAfterOutletActivation();
}
private scrollDocumentToTop(): void {
const scrollingElement = this.document.scrollingElement ?? this.document.documentElement;
scrollingElement.scrollTop = 0;
scrollingElement.scrollLeft = 0;
this.document.documentElement.scrollTop = 0;
this.document.documentElement.scrollLeft = 0;
this.document.body.scrollTop = 0;
this.document.body.scrollLeft = 0;
this.document.defaultView?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.document.body.style.overflow = ''; this.document.body.style.overflow = '';
this.document.documentElement.style.overflow = ''; this.document.documentElement.style.overflow = '';