在我的 Angular 2 应用程序中,当我向下滚动页面并单击页面底部的链接时,它确实会更改路线并将我带到下一页,但不会滚动到页面顶部。结果,如果第一页很长,而第二页内容很少,就会给人一种第二页缺少内容的印象。因为只有当用户滚动到页面顶部时内容才可见。
我可以在组件的 ngInit 中将窗口滚动到页面顶部,但是,有没有更好的解决方案可以自动处理我的应用程序中的所有路由?
RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })
Angular 6.1 及更高版本:
Angular 6.1(于 2018-07-25 发布)通过称为“路由器滚动位置恢复”的功能添加了内置支持来处理此问题。如官方Angular blog所述,您只需在路由器配置中启用此功能,如下所示:
RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})
此外,该博客指出“预计这将成为未来主要版本的默认设置”。到目前为止,这还没有发生(从 Angular 11.0 开始),但最终您根本不需要在代码中做任何事情,而且开箱即用就可以正常工作。
您可以在 the official docs 中查看有关此功能以及如何自定义此行为的更多详细信息。
Angular 6.0 及更早版本:
虽然@GuilhermeMeireles 的出色答案解决了原始问题,但它引入了一个新问题,它打破了您在向后或向前导航时所期望的正常行为(使用浏览器按钮或通过代码中的位置)。预期的行为是,当您导航回页面时,它应该保持向下滚动到单击链接时的相同位置,但是在到达每个页面时滚动到顶部显然打破了这种预期。
下面的代码扩展了检测这种导航的逻辑,方法是订阅 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);
}
});
}
}
您可以在主组件上注册路由更改侦听器,并在路由更改时滚动到顶部。
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)
});
}
}
window.scrollTo(0, 0)
比 document.body.scrollTop = 0;
更简洁,IMO 更易读。
$("body").animate({ scrollTop: 0 }, 1000);
而不是 window.scrollTo(0, 0)
以动画平滑滚动到顶部
从 Angular 6.1 开始,您现在可以避免麻烦并将 extraOptions
作为第二个参数传递给您的 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 { }
您可以利用 observable filter
方法更简洁地编写此代码:
this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
this.window.scrollTo(0, 0);
});
如果您在使用 Angular Material 2 sidenav 时遇到滚动到顶部的问题,这将有所帮助。窗口或文档正文没有滚动条,因此您需要获取 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 现在有一个 scrolling package 可能有助于处理滚动。
document.querySelector('.mat-sidenav-content .content-div').scrollTop = 0;
Angular 最近引入了一项新功能,在 Angular 路由模块内部进行如下更改
@NgModule({
imports: [RouterModule.forRoot(routes,{
scrollPositionRestoration: 'top'
})],
如果您有服务器端渲染,则应注意不要在不存在该变量的服务器上使用 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')
constructor
中注入 PLATFORM_ID
并将此值作为 de isPlatformBrowser
方法中的参数吗?
isPlatformBrowser
是一个函数,并且永远是真实的。我现在已经编辑过了。
最佳答案位于 Angular GitHub 讨论中 (Changing route doesn't scroll to top in the new page)。
也许您只想在根路由器更改中进入顶部(而不是在子路由器中,因为您可以在 fe 选项卡集中使用延迟加载来加载路由)
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)
}
JoniJnm (original post) 的完整学分
只需点击操作即可轻松完成
在您的主要组件 html 中引用#scrollContainer
<div class="main-container" #scrollContainer>
<router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
</div>
在主要组件 .ts
onActivate(e, scrollContainer) {
scrollContainer.scrollTop = 0;
}
scrollContainer
第一个节点中,您可能需要在对象中挖掘一点,对我来说它真正起作用的是 scrollContainer .scrollable._elementRef.nativeElement.scrollTop = 0
从 Angular 6.1 开始,路由器提供了一个名为 scrollPositionRestoration
的 configuration option,旨在满足这种情况。
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
}),
...
]
除了如下所示的 @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
这是我想出的解决方案。我将 LocationStrategy 与 Router 事件配对。使用 LocationStrategy 设置一个布尔值以了解用户当前何时遍历浏览器历史记录。这样,我就不必存储一堆 URL 和 y-scroll 数据(无论如何都不能很好地工作,因为每个数据都是基于 URL 替换的)。当用户决定按住浏览器上的后退或前进按钮并后退或前进多个页面而不仅仅是一个页面时,这也解决了边缘情况。
PS 我只在最新版本的 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;
}
});
}
}
如果您只需将页面滚动到顶部,您可以这样做(不是最好的解决方案,但速度很快)
document.getElementById('elementId').scrollTop = 0;
该解决方案基于@FernandoEcheverria 和@GuilhermeMeireles 的解决方案,但更简洁,并且与Angular 路由器提供的popstate 机制配合使用。这允许存储和恢复多个连续导航的滚动级别。
我们将每个导航状态的滚动位置存储在地图 scrollLevels
中。一旦有 popstate 事件,即将恢复的状态的 ID 由 Angular 路由器提供: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);
}
}
});
}
}
scrollTop
与 scrollY
。
如果您使用相同的路由加载不同的组件,那么您可以使用 ViewportScroller 来实现相同的目的。
import { ViewportScroller } from '@angular/common';
constructor(private viewportScroller: ViewportScroller) {}
this.viewportScroller.scrollToPosition([0, 0]);
您还可以在 Route.ts 中使用 scrollOffset。参考。 Router ExtraOptions
@NgModule({
imports: [
SomeModule.forRoot(
SomeRouting,
{
scrollPositionRestoration: 'enabled',
scrollOffset:[0,0]
})],
exports: [RouterModule]
})
对于所有正在寻找解决方案并阅读这篇文章的人。这
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
}),
...
]
没有回答主题的问题。如果我们查看 Angular 源代码,那么我们可以读到有趣的行:
https://i.stack.imgur.com/DpJ8D.png
所以这些东西只适用于后退导航。解决方案之一可能是这样的:
constructor(router: Router) {
router.events
.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))
.subscribe(() => {
this.document.querySelector('#top').scrollIntoView();
});
}
这将查看每个导航到具有该 id 的 div 并滚动到它;
另一种方法是使用相同的逻辑,但在装饰器或指令的帮助下,您可以选择滚动顶部的位置和时间;
对于 iphone/ios Safari,您可以使用 setTimeout 进行包装
setTimeout(function(){
window.scrollTo(0, 1);
}, 0);
height: 100vh + 1px;
嗨,伙计们,这在角度 4 中对我有用。您只需参考父级即可滚动更改路由器`
布局.component.pug
.wrapper(#outlet="")
router-outlet((activate)='routerActivate($event,outlet)')
布局.component.ts
public routerActivate(event,outlet){
outlet.scrollTop = 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) } }) }
使用 Router
本身会导致您无法完全克服以保持一致的浏览器体验的问题。在我看来,最好的方法是只使用自定义 directive
并让它在点击时重置滚动。这样做的好处是,如果您在点击的 url
上,页面也会滚动回顶部。这与普通网站一致。基本的 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>
这对于大多数用例来说已经足够了,但我可以想象由此产生的一些问题:
由于使用了窗口,因此不适用于通用
对变更检测的速度影响很小,因为它是由每次点击触发的
无法禁用此指令
克服这些问题实际上很容易:
@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
事件。如果您想在自定义指令/模板中实现所有这些,这将使代码更加复杂,并且有点晦涩难懂。这就是为什么我写了一个小 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);
}
}
}
这对我来说最适合所有导航更改,包括哈希导航
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);
}
});
}
这段代码背后的主要思想是将所有访问过的 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;
}
});
窗口滚动顶部 window.pageYOffset 和 document.documentElement.scrollTop 在所有情况下都返回相同的结果。 IE 9 以下不支持 window.pageYOffset。
app.component.ts
import { Component, HostListener, ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
isShow: boolean;
topPosToStartShowing = 100;
@HostListener('window:scroll')
checkScroll() {
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
console.log('[scroll]', scrollPosition);
if (scrollPosition >= this.topPosToStartShowing) {
this.isShow = true;
} else {
this.isShow = false;
}
}
gotoTop() {
window.scroll({
top: 0,
left: 10,
behavior: 'smooth'
});
}
}
app.component.html
<style>
p {
font-family: Lato;
}
button {
position: fixed;
bottom: 5px;
right: 5px;
font-size: 20px;
text-align: center;
border-radius: 5px;
outline: none;
}
</style>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<button *ngIf="isShow" (click)="gotoTop()">👆</button>
lastRoutePath?: string;
ngOnInit(): void {
void this.router.events.forEach((event) => {
if (event instanceof ActivationEnd) {
if (this.lastRoutePath !== event.snapshot.routeConfig?.path) {
window.scrollTo(0, 0);
}
this.lastRoutePath = event.snapshot.routeConfig?.path;
}
});
}
如果您停留在同一页面上,它不会滚动到顶部,而只会更改 slug / id 或其他任何内容
在执行时在下面调用它,它为我工作 %100
document.body.scrollTop = 0;
如
this.brandCollectionList$.subscribe((response) => {
document.body.scrollTop = 0;
});
不定期副业成功案例分享
body
?