Reset scroll position after navigation. Fixes #10482 (#12423)

This commit is contained in:
Steve Sanderson 2019-07-24 10:25:59 -07:00 committed by GitHub
parent 8b7fcf1f76
commit 54da777b7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 4 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ interface BrowserRendererRegistry {
[browserRendererId: number]: BrowserRenderer;
}
const browserRenderers: BrowserRendererRegistry = {};
let shouldResetScrollAfterNextBatch = false;
export function attachRootComponentToLogicalElement(browserRendererId: number, logicalElement: LogicalElement, componentId: number): void {
@ -67,4 +68,20 @@ export function renderBatch(browserRendererId: number, batch: RenderBatch): void
const eventHandlerId = batch.disposedEventHandlerIdsEntry(disposedEventHandlerIdsValues, i);
browserRenderer.disposeEventHandler(eventHandlerId);
}
resetScrollIfNeeded();
}
export function resetScrollAfterNextBatch() {
shouldResetScrollAfterNextBatch = true;
}
function resetScrollIfNeeded() {
if (shouldResetScrollAfterNextBatch) {
shouldResetScrollAfterNextBatch = false;
// This assumes the scroller is on the window itself. There isn't a general way to know
// if some other element is playing the role of the primary scroll region.
window.scrollTo && window.scrollTo(0, 0);
}
}

View File

@ -1,4 +1,5 @@
import '@dotnet/jsinterop';
import { resetScrollAfterNextBatch } from '../Rendering/Renderer';
let hasRegisteredNavigationInterception = false;
let hasRegisteredNavigationEventListeners = false;
@ -81,6 +82,13 @@ export function navigateTo(uri: string, forceLoad: boolean) {
}
function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) {
// Since this was *not* triggered by a back/forward gesture (that goes through a different
// code path starting with a popstate event), we don't want to preserve the current scroll
// position, so reset it.
// To avoid ugly flickering effects, we don't want to change the scroll position until the
// we render the new page. As a best approximation, wait until the next batch.
resetScrollAfterNextBatch();
history.pushState(null, /* ignored title */ '', absoluteInternalHref);
notifyLocationChanged(interceptedLink);
}

View File

@ -418,6 +418,40 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text);
}
[Fact]
public void ResetsScrollPositionWhenPerformingInternalNavigation_LinkClick()
{
SetUrlViaPushState("/LongPage1");
var app = MountTestComponent<TestRouter>();
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
BrowserScrollY = 500;
Browser.True(() => BrowserScrollY > 300); // Exact position doesn't matter
app.FindElement(By.LinkText("Long page 2")).Click();
Browser.Equal("This is another long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
Browser.Equal(0, () => BrowserScrollY);
}
[Fact]
public void ResetsScrollPositionWhenPerformingInternalNavigation_ProgrammaticNavigation()
{
SetUrlViaPushState("/LongPage1");
var app = MountTestComponent<TestRouter>();
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
BrowserScrollY = 500;
Browser.True(() => BrowserScrollY > 300); // Exact position doesn't matter
app.FindElement(By.Id("go-to-longpage2")).Click();
Browser.Equal("This is another long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
Browser.Equal(0, () => BrowserScrollY);
}
private long BrowserScrollY
{
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");
set => ((IJavaScriptExecutor)Browser).ExecuteScript($"window.scrollTo(0, {value})");
}
private string SetUrlViaPushState(string relativeUri)
{
var pathBaseWithoutHash = ServerPathBase.Split('#')[0];

View File

@ -18,6 +18,8 @@
<li><NavLink href="/subdir/Other#blah">Other with hash</NavLink></li>
<li><NavLink href="/subdir/WithParameters/Name/Abc">With parameters</NavLink></li>
<li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With more parameters</NavLink></li>
<li><NavLink href="/subdir/LongPage1">Long page 1</NavLink></li>
<li><NavLink href="/subdir/LongPage2">Long page 2</NavLink></li>
</ul>
<button id="do-navigation" @onclick=@(x => uriHelper.NavigateTo("Other"))>

View File

@ -0,0 +1,13 @@
@page "/LongPage1"
@inject IUriHelper UriHelper
<div id="test-info">This is a long page you can scroll.</div>
<div style="border: 2px dashed red; margin: 1rem; padding: 1rem; height: 1500px;">
Scroll past me to find the links
</div>
<button id="go-to-longpage2" @onclick="@(() => UriHelper.NavigateTo("LongPage2"))">
Navigate programmatically to long page 2
</button>
<Links />

View File

@ -0,0 +1,8 @@
@page "/LongPage2"
<div id="test-info">This is another long page you can scroll.</div>
<div style="border: 2px dashed blue; margin: 1rem; padding: 1rem; height: 1500px;">
Scroll past me to find the links
</div>
<Links />