Avoid UI flickering during transition from prerendered to live content

This commit is contained in:
Steve Sanderson 2019-02-22 13:53:45 +00:00
parent 6a57218e14
commit 5315446054
3 changed files with 24 additions and 12 deletions

View File

@ -7,6 +7,7 @@ const selectValuePropname = '_blazorSelectValue';
const sharedTemplateElemForParsing = document.createElement('template');
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const preventDefaultEvents: { [eventType: string]: boolean } = { submit: true };
const rootComponentsPendingFirstRender: { [componentId: number]: Element } = {};
export class BrowserRenderer {
private eventDelegator: EventDelegator;
@ -19,7 +20,9 @@ export class BrowserRenderer {
}
public attachRootComponentToElement(componentId: number, element: Element) {
this.attachComponentToElement(componentId, toLogicalElement(element));
// 'allowExistingContents' to keep any prerendered content until we do the first client-side render
this.attachComponentToElement(componentId, toLogicalElement(element, /* allowExistingContents */ true));
rootComponentsPendingFirstRender[componentId] = element;
}
public updateComponent(batch: RenderBatch, componentId: number, edits: ArraySegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>) {
@ -28,6 +31,13 @@ export class BrowserRenderer {
throw new Error(`No element is currently associated with component ${componentId}`);
}
// On the first render for each root component, clear any existing content (e.g., prerendered)
const rootElementToClear = rootComponentsPendingFirstRender[componentId];
if (rootElementToClear) {
delete rootComponentsPendingFirstRender[componentId];
clearElement(rootElementToClear);
}
this.applyEdits(batch, element, 0, edits, referenceFrames);
}
@ -368,3 +378,10 @@ function raiseEvent(event: Event, browserRendererId: number, eventHandlerId: num
eventDescriptor,
JSON.stringify(eventArgs.data));
}
function clearElement(element: Element) {
let childNode: Node | null;
while (childNode = element.firstChild) {
element.removeChild(childNode);
}
}

View File

@ -28,9 +28,12 @@
const logicalChildrenPropname = createSymbolOrFallback('_blazorLogicalChildren');
const logicalParentPropname = createSymbolOrFallback('_blazorLogicalParent');
export function toLogicalElement(element: Element) {
if (element.childNodes.length > 0) {
throw new Error('New logical elements must start empty');
export function toLogicalElement(element: Element, allowExistingContents?: boolean) {
// Normally it's good to assert that the element has started empty, because that's the usual
// situation and we probably have a bug if it's not. But for the element that contain prerendered
// root components, we want to let them keep their content until we replace it.
if (element.childNodes.length > 0 && !allowExistingContents) {
throw new Error('New logical elements must start empty, or allowExistingContents must be true');
}
element[logicalChildrenPropname] = [];

View File

@ -16,7 +16,6 @@ export function attachRootComponentToElement(browserRendererId: number, elementS
if (!browserRenderer) {
browserRenderer = browserRenderers[browserRendererId] = new BrowserRenderer(browserRendererId);
}
clearElement(element);
browserRenderer.attachRootComponentToElement(componentId, element);
}
@ -57,10 +56,3 @@ export function renderBatch(browserRendererId: number, batch: RenderBatch) {
browserRenderer.disposeEventHandler(eventHandlerId);
}
}
function clearElement(element: Element) {
let childNode: Node | null;
while (childNode = element.firstChild) {
element.removeChild(childNode);
}
}