Skip to content

Commit 9dc88f1

Browse files
Reset scroll position after navigation. Fixes #10482
1 parent 077df0e commit 9dc88f1

File tree

7 files changed

+83
-1
lines changed

7 files changed

+83
-1
lines changed

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Rendering/Renderer.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface BrowserRendererRegistry {
99
[browserRendererId: number]: BrowserRenderer;
1010
}
1111
const browserRenderers: BrowserRendererRegistry = {};
12+
let shouldResetScrollAfterNextBatch = false;
1213

1314
export function attachRootComponentToLogicalElement(browserRendererId: number, logicalElement: LogicalElement, componentId: number): void {
1415

@@ -67,4 +68,20 @@ export function renderBatch(browserRendererId: number, batch: RenderBatch): void
6768
const eventHandlerId = batch.disposedEventHandlerIdsEntry(disposedEventHandlerIdsValues, i);
6869
browserRenderer.disposeEventHandler(eventHandlerId);
6970
}
71+
72+
resetScrollIfNeeded();
73+
}
74+
75+
export function resetScrollAfterNextBatch() {
76+
shouldResetScrollAfterNextBatch = true;
77+
}
78+
79+
function resetScrollIfNeeded() {
80+
if (shouldResetScrollAfterNextBatch) {
81+
shouldResetScrollAfterNextBatch = false;
82+
83+
// This assumes the scroller is on the window itself. There isn't a general way to know
84+
// if some other element is playing the role of the primary scroll region.
85+
window.scrollTo && window.scrollTo(0, 0);
86+
}
7087
}

src/Components/Web.JS/src/Services/UriHelper.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import '@dotnet/jsinterop';
2+
import { resetScrollAfterNextBatch } from '../Rendering/Renderer';
23

34
let hasRegisteredNavigationInterception = false;
45
let hasRegisteredNavigationEventListeners = false;
@@ -81,6 +82,13 @@ export function navigateTo(uri: string, forceLoad: boolean) {
8182
}
8283

8384
function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) {
85+
// Since this was *not* triggered by a back/forward gesture (that goes through a different
86+
// code path starting with a popstate event), we don't want to preserve the current scroll
87+
// position, so reset it.
88+
// To avoid ugly flickering effects, we don't want to change the scroll position until the
89+
// we render the new page. As a best approximation, wait until the next batch.
90+
resetScrollAfterNextBatch();
91+
8492
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
8593
notifyLocationChanged(interceptedLink);
8694
}

src/Components/test/E2ETest/Tests/RoutingTest.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,40 @@ public void RoutingToComponentOutsideMainAppDoesNotWork()
418418
Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text);
419419
}
420420

421+
[Fact]
422+
public void ResetsScrollPositionWhenPerformingInternalNavigation_LinkClick()
423+
{
424+
SetUrlViaPushState("/LongPage1");
425+
var app = MountTestComponent<TestRouter>();
426+
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
427+
BrowserScrollY = 500;
428+
Browser.True(() => BrowserScrollY > 300); // Exact position doesn't matter
429+
430+
app.FindElement(By.LinkText("Long page 2")).Click();
431+
Browser.Equal("This is another long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
432+
Browser.Equal(0, () => BrowserScrollY);
433+
}
434+
435+
[Fact]
436+
public void ResetsScrollPositionWhenPerformingInternalNavigation_ProgrammaticNavigation()
437+
{
438+
SetUrlViaPushState("/LongPage1");
439+
var app = MountTestComponent<TestRouter>();
440+
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
441+
BrowserScrollY = 500;
442+
Browser.True(() => BrowserScrollY > 300); // Exact position doesn't matter
443+
444+
app.FindElement(By.Id("go-to-longpage2")).Click();
445+
Browser.Equal("This is another long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
446+
Browser.Equal(0, () => BrowserScrollY);
447+
}
448+
449+
private long BrowserScrollY
450+
{
451+
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");
452+
set => ((IJavaScriptExecutor)Browser).ExecuteScript($"window.scrollTo(0, {value})");
453+
}
454+
421455
private string SetUrlViaPushState(string relativeUri)
422456
{
423457
var pathBaseWithoutHash = ServerPathBase.Split('#')[0];

src/Components/test/testassets/BasicTestApp/RouterTest/Links.razor

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
<li><NavLink href="/subdir/Other#blah">Other with hash</NavLink></li>
1919
<li><NavLink href="/subdir/WithParameters/Name/Abc">With parameters</NavLink></li>
2020
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
21+
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
22+
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
2123
</ul>
2224

2325
<button id="do-navigation" @onclick=@(x => uriHelper.NavigateTo("Other"))>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@page "/LongPage1"
2+
@inject IUriHelper UriHelper
3+
<div id="test-info">This is a long page you can scroll.</div>
4+
5+
<div style="border: 2px dashed red; margin: 1rem; padding: 1rem; height: 1500px;">
6+
Scroll past me to find the links
7+
</div>
8+
9+
<button id="go-to-longpage2" @onclick="@(() => UriHelper.NavigateTo("LongPage2"))">
10+
Navigate programmatically to long page 2
11+
</button>
12+
13+
<Links />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@page "/LongPage2"
2+
<div id="test-info">This is another long page you can scroll.</div>
3+
4+
<div style="border: 2px dashed blue; margin: 1rem; padding: 1rem; height: 1500px;">
5+
Scroll past me to find the links
6+
</div>
7+
8+
<Links />

0 commit comments

Comments
 (0)