mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(router): Scroller should scroll as soon as change detection completes (#60086)
Using `setTimeout` to delay scrolling can result in scrolling in the next frame and cause noticeable flicker. This commit races rAF and timeout to ensure scroll happens before the render. fixes #53985 PR Close #60086
This commit is contained in:
@@ -7,7 +7,14 @@
|
||||
*/
|
||||
|
||||
import {ViewportScroller} from '@angular/common';
|
||||
import {Injectable, InjectionToken, NgZone, OnDestroy} from '@angular/core';
|
||||
import {
|
||||
EnvironmentInjector,
|
||||
inject,
|
||||
Injectable,
|
||||
InjectionToken,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import {Unsubscribable} from 'rxjs';
|
||||
|
||||
import {
|
||||
@@ -105,21 +112,30 @@ export class RouterScroller implements OnDestroy {
|
||||
routerEvent: NavigationEnd | NavigationSkipped,
|
||||
anchor: string | null,
|
||||
): void {
|
||||
this.zone.runOutsideAngular(() => {
|
||||
this.zone.runOutsideAngular(async () => {
|
||||
// The scroll event needs to be delayed until after change detection. Otherwise, we may
|
||||
// attempt to restore the scroll position before the router outlet has fully rendered the
|
||||
// component by executing its update block of the template function.
|
||||
setTimeout(() => {
|
||||
this.zone.run(() => {
|
||||
this.transitions.events.next(
|
||||
new Scroll(
|
||||
routerEvent,
|
||||
this.lastSource === 'popstate' ? this.store[this.restoredId] : null,
|
||||
anchor,
|
||||
),
|
||||
);
|
||||
});
|
||||
}, 0);
|
||||
//
|
||||
// #57109 (we need to wait at least a macrotask before scrolling. AfterNextRender resolves in microtask event loop with Zones)
|
||||
// We could consider _also_ waiting for a render promise though one should have already happened or been scheduled by this point
|
||||
// and should definitely happen before rAF/setTimeout.
|
||||
// #53985 (cannot rely solely on setTimeout because a frame may paint before the timeout)
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve);
|
||||
if (typeof requestAnimationFrame !== 'undefined') {
|
||||
requestAnimationFrame(resolve);
|
||||
}
|
||||
});
|
||||
this.zone.run(() => {
|
||||
this.transitions.events.next(
|
||||
new Scroll(
|
||||
routerEvent,
|
||||
this.lastSource === 'popstate' ? this.store[this.restoredId] : null,
|
||||
anchor,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user