Blazor Virtualize наконец умеет в элементы переменной высоты в .NET 11
ASP.NET Core в .NET 11 Preview 3 учит компонент Virtualize измерять элементы в runtime, устраняя spacing- и scroll-джиттер, вызванный допущениями о равной высоте.
Любой, кто использовал Virtualize<TItem> для чат-лога, ленты карточек или панели уведомлений, видел тот же баг: элементы прыгают при прокрутке, thumb scrollbar скачет, и вы заканчиваете неуклюжими пробелами или перекрытиями. Корневая причина всегда была одна. Virtualize предполагал, что каждая row одинаковой высоты, и использовал это единственное число, чтобы вычислить окно прокрутки. .NET 11 Preview 3 наконец это чинит: компонент теперь измеряет элементы в runtime и подстраивает виртуальный viewport под высоты, которые реально приземляются в DOM.
Почему старое поведение ломало реальные UI
Оригинальное API принуждало выбрать скаляр через ItemSize. Если ваши элементы были 48px высотой, вы ставили 48. Blazor тогда умножал количество элементов на 48, чтобы размерить прокручиваемую область, и рендерил только те rows, у которых вычисленная позиция top пересекала viewport. Как только ваши rows содержали body переменной длины, обёртываемую цитату или responsive-изображение, математика переставала соответствовать реальности, и браузер с Blazor дрались за placement.
<Virtualize Items="messages" Context="message">
<article class="message-card">
<h4>@message.Author</h4>
<p>@message.Text</p>
</article>
</Virtualize>
Этот сниппет - именно тот сценарий, который раньше вёл себя плохо. Короткая однострочка и ответ на пять абзацев делят один row-слот, так что scroll offsets дрейфуют по мере вашего движения по списку.
Измерение отрендеренного DOM
В .NET 11 Preview 3 Virtualize теперь отслеживает измеренные размеры элементов в runtime и подаёт их обратно в свои расчёты spacer. Больше не нужно устанавливать ItemSize в значение, соответствующее худшему случаю, и больше не нужно устанавливать overflow: hidden на детях, чтобы впихнуть их в фиксированный box. Компонент всё ещё принимает начальный hint размера, но обращается с ним как с начальной оценкой, а не абсолютной истиной.
Второе изменение - дефолт OverscanCount. Virtualize раньше рендерил три элемента выше и ниже viewport. В Preview 3 этот дефолт прыгает до 15, чтобы было достаточно измеренных элементов для стабилизации оценки высоты до того, как пользователь проскроллит в неизмеренную территорию.
<Virtualize Items="messages" Context="message" OverscanCount="30">
<article class="message-card">
<h4>@message.Author</h4>
<p>@message.Text</p>
</article>
</Virtualize>
Поднять OverscanCount выше теперь легитимная tuning-ручка для лент с дико разной высотой элементов. Цена - рендерить больше off-screen DOM, но в обмен вы получаете более плавную прокрутку и стабильный scrollbar.
QuickGrid держит старый дефолт
Если используете QuickGrid, ничего не меняется. Компонент пинит свой OverscanCount на 3, потому что row grid нарочно единообразны, и рендер 30 скрытых rows на каждый тик scroll спалил бы производительность для таблиц с сотнями колонок. Это намеренно: новые дефолты целятся в компоненты, где старое предположение было по-настоящему неверным.
Что поменять в существующих приложениях
Выбросьте значение ItemSize, если вы его ставили только ради прикрытия переменных высот, поскольку измеренный путь там строго лучше. Проревизуйте любой CSS, который добавляли, чтобы впихивать детей в фиксированный box. И профилируйте прокрутку до дальнейшей подкрутки OverscanCount, потому что 15 уже большой скачок с 3.
Реализация живёт в dotnet/aspnetcore#64964. Хватайте .NET 11 Preview 3, и в следующий раз, когда кто-то спросит, почему chat log скроллится странно, у вас будет одним workaround меньше для объяснения.