-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Description
Since .NET 10 (PR #60296), Blazor's enhanced navigation automatically resets scroll when navigating between different pages. However, the scroll happens before the new content is rendered, causing a visual flash where users see old page content during the scroll animation.
Problem
When navigating from Page A to Page B:
- User clicks link
- Scroll starts immediately
- While scrolling, old page content (Page A) is still visible
- New content (Page B) suddenly pops in during/after scroll
- Visual flash/glitch ruins the user experience
This makes enhanced navigation feel janky compared to traditional navigation.
Steps to Reproduce
- Create two pages with different routes and long content
- Scroll down on Page 1
- Click link to Page 2
- Observe: Scroll starts while Page 1 content is still visible, then Page 2 content suddenly appears
Example:
@page "/page1"
<h1>Page 1</h1>
<a href="/page2">Go to Page 2</a>
<div style="height: 2000px">Long content...</div>
@page "/page2"
<h1>Page 2</h1>
<a href="/page1">Go to Page 1</a>
<div style="height: 2000px">Long content...</div>
Video
scroll-bug.mp4
Note: In the video, browser network is throttled to 4G to make the issue more visible. The slower the connection, the more noticeable the flash becomes, but it occurs on all connection speeds.
Root Cause
Looking at PR #60296, the resetScrollIfNeeded()
function is called synchronously during navigation, before the DOM update completes:
async function handleInternalNavigation(renderBatch, interceptedRouter) {
if (shouldHandleEnhancedNav) {
resetScrollIfNeeded(renderBatch); // ⚠️ Too early - DOM not updated yet
const diffResult = await performInternalEnhancedNavigation(renderBatch);
// ...
}
}
The scroll runs before the browser paints the new content.
Proposed Fix
Delay scroll until after browser paints new content using requestAnimationFrame
:
function resetScrollIfNeeded(renderBatch) {
const shouldScroll = /* ... existing logic ... */;
if (shouldScroll) {
// Wait for browser to paint new content
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.scrollTo(0, 0);
});
});
}
}
Double RAF ensures scroll happens after the next paint, when new content is visible.
Alternative: Provide API to disable automatic scroll
If fixing the timing is complex, at least provide an option to disable the automatic scroll behavior entirely:
Current Workaround
After trying multiple approaches (setTimeout
, requestAnimationFrame
, CSS hiding), we found only one ugly (but working) solution: intercept and block window.scrollTo
during navigation.
let _originalScrollTo = null;
// Before navigation starts (enhancednavigationstart event)
if (!_originalScrollTo) {
_originalScrollTo = window.scrollTo;
}
// Block Blazor's scrollTo during navigation
window.scrollTo = function() {
console.log('Blocked premature scroll');
};
// After navigation completes (enhancedload event)
window.scrollTo = _originalScrollTo; // Restore original
// Now manually handle scroll as needed
window.scrollTo({ top: 0, behavior: 'smooth' });
This works but has significant downsides, its an ugly hack and you need to carefully manage your eventlistener to makee sure to apply scrollTo when needed.
Impact
Affects all Blazor SSR apps with enhanced navigation when:
- Navigating between different routes
- User is not at top of page
- Pages have visually distinct content
This is the default behavior in .NET 10.
Environment
- .NET Version: 10.0 (introduced in PR Scroll in enhanced navigation behaves same as in client side routing #60296)
- Browser: All browsers
- Reproduction: 100% reproducible
Related
Expected Behavior
No response
Steps To Reproduce
No response
Exceptions (if any)
No response
.NET Version
No response
Anything else?
No response