Skip to content

Commit 5315446

Browse files
Avoid UI flickering during transition from prerendered to live content
1 parent 6a57218 commit 5315446

File tree

3 files changed

+24
-12
lines changed

3 files changed

+24
-12
lines changed

src/Components/Browser.JS/src/Rendering/BrowserRenderer.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const selectValuePropname = '_blazorSelectValue';
77
const sharedTemplateElemForParsing = document.createElement('template');
88
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
99
const preventDefaultEvents: { [eventType: string]: boolean } = { submit: true };
10+
const rootComponentsPendingFirstRender: { [componentId: number]: Element } = {};
1011

1112
export class BrowserRenderer {
1213
private eventDelegator: EventDelegator;
@@ -19,7 +20,9 @@ export class BrowserRenderer {
1920
}
2021

2122
public attachRootComponentToElement(componentId: number, element: Element) {
22-
this.attachComponentToElement(componentId, toLogicalElement(element));
23+
// 'allowExistingContents' to keep any prerendered content until we do the first client-side render
24+
this.attachComponentToElement(componentId, toLogicalElement(element, /* allowExistingContents */ true));
25+
rootComponentsPendingFirstRender[componentId] = element;
2326
}
2427

2528
public updateComponent(batch: RenderBatch, componentId: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
@@ -28,6 +31,13 @@ export class BrowserRenderer {
2831
throw new Error(`No element is currently associated with component ${componentId}`);
2932
}
3033

34+
// On the first render for each root component, clear any existing content (e.g., prerendered)
35+
const rootElementToClear = rootComponentsPendingFirstRender[componentId];
36+
if (rootElementToClear) {
37+
delete rootComponentsPendingFirstRender[componentId];
38+
clearElement(rootElementToClear);
39+
}
40+
3141
this.applyEdits(batch, element, 0, edits, referenceFrames);
3242
}
3343

@@ -368,3 +378,10 @@ function raiseEvent(event: Event, browserRendererId: number, eventHandlerId: num
368378
eventDescriptor,
369379
JSON.stringify(eventArgs.data));
370380
}
381+
382+
function clearElement(element: Element) {
383+
let childNode: Node | null;
384+
while (childNode = element.firstChild) {
385+
element.removeChild(childNode);
386+
}
387+
}

src/Components/Browser.JS/src/Rendering/LogicalElements.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
const logicalChildrenPropname = createSymbolOrFallback('_blazorLogicalChildren');
2929
const logicalParentPropname = createSymbolOrFallback('_blazorLogicalParent');
3030

31-
export function toLogicalElement(element: Element) {
32-
if (element.childNodes.length > 0) {
33-
throw new Error('New logical elements must start empty');
31+
export function toLogicalElement(element: Element, allowExistingContents?: boolean) {
32+
// Normally it's good to assert that the element has started empty, because that's the usual
33+
// situation and we probably have a bug if it's not. But for the element that contain prerendered
34+
// root components, we want to let them keep their content until we replace it.
35+
if (element.childNodes.length > 0 && !allowExistingContents) {
36+
throw new Error('New logical elements must start empty, or allowExistingContents must be true');
3437
}
3538

3639
element[logicalChildrenPropname] = [];

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function attachRootComponentToElement(browserRendererId: number, elementS
1616
if (!browserRenderer) {
1717
browserRenderer = browserRenderers[browserRendererId] = new BrowserRenderer(browserRendererId);
1818
}
19-
clearElement(element);
2019
browserRenderer.attachRootComponentToElement(componentId, element);
2120
}
2221

@@ -57,10 +56,3 @@ export function renderBatch(browserRendererId: number, batch: RenderBatch) {
5756
browserRenderer.disposeEventHandler(eventHandlerId);
5857
}
5958
}
60-
61-
function clearElement(element: Element) {
62-
let childNode: Node | null;
63-
while (childNode = element.firstChild) {
64-
element.removeChild(childNode);
65-
}
66-
}

0 commit comments

Comments
 (0)