Skip to content

Allow configuring virtualization spacer element type #42449

@SteveSandersonMS

Description

@SteveSandersonMS

Background and Motivation

Blazor's <Virtualize> component produces a DOM structure like this:

<div style="height: 0px"></div>
... user-rendered element 1 ...
... user-rendered element 2 ...
...
... user-rendered element N ...
<div style="height: 1234px"></div>

The top and bottom elements there are called "spacers" and their job is to:

  1. Have an explicit height to cause the scroll range to be correct and represent the whole virtualized UI
  2. Detect when scrolling is bringing them close to the visible viewport so we know to change the set of rendered elements

The problem is that it's currently hardcoded to use div elements for these spacers. While pretty general-purpose, there are some cases where div is not technically allowed, such as directly inside a tbody.

In the past we decided it works well enough anyway, since the browser doesn't really care if you put a div inside a tbody via JavaScript. However this does cause a problem if you do it in the initial HTML page, such as when prerendering. The browser's HTML parser will think hey that's not right, let me fix that for you and gives you a DOM structure like this:

<div style="height: 0px"></div>
<div style="height: 12345px"></div>
<table>
   <tbody>
        ... user-rendered element 1 ...
        ... user-rendered element 2 ...
        ...
        ... user-rendered element N ...
   </tbody>
<table>

... i.e., it moves the spacers outside the table. This doesn't cause much of a problem because as soon as Blazor starts up interactively, it rebuilds the DOM anyway and puts things in the right places. But during the time between prerendering and interactivity, you may see a flash of malformed content.

Proposed API

public class Virtualize<TItem>
{
+    /// <summary>
+    /// Gets or sets the tag name of the HTML element that will be used as the virtualization spacer.
+    /// One such element will be rendered before the visible items, and one more after them, using
+    /// an explicit "height" style to control the scroll range.
+    ///
+    /// The default value is "div". If you are placing the <see cref="Virtualize{TItem}"/> instance inside
+    /// an element that requires a specific child tag name, consider setting that here. For example when
+    /// rendering inside a "tbody", consider setting <see cref="SpacerElement"/> to the value "tr".
+    /// </summary>
+    [Parameter]
+    public string SpacerElement { get; set; } = "div";
}

Usage Examples

<Virtualize SpacerElement="tr">...</Virtualize>

Alternative Designs

We could avoid having this be configurable, and instead JS logic that looks at the actual parent element at runtime and decides whether to use a div, a tr, or whatever. That's nice but the main problem is it would be a breaking change. I know some people already are using CSS to target the spacer element to do particular things with it like set a background color in case it becomes visible during fast scrolling.

Update: No, that alternative is total nonsense, even ignoring the breaking change aspect. I just realised it wouldn't work in prerendering (as there's no JS running) and the whole point of this fix is to work with prerendering. It has to be done in .NET code. I'm not aware of a sensible alternative design.

Risks

Nothing significant. If people do something nonsensical such as entering an illegal tag name value (e.g., an empty string), the browser will already give them a sensible message at runtime when Blazor tries to use it when constructing an element.

Metadata

Metadata

Labels

api-approvedAPI was approved in API review, it can be implementedarea-blazorIncludes: Blazor, Razor Componentsfeature-prerenderingIssues related to prerendering blazor components

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions