New to Telerik UI for Blazor? Download free 30-day trial

Virtual Scrolling

Virtual scrolling provides an alternative to paging. Instead of utilizing a pager, the user scrolls vertically through all records in the data source.

To enhance rendering performance, the Grid reuses the same set of HTML elements. As the next data loads, a loading indicator appears on the cells. If the user scrolls back up after scrolling down to the next set of rows, the previous data reloads from the data source, similar to regular paging, with the scroll distance determining the data to be loaded.

You can also use virtual scrolling for the Grid columns. See the Column Virtualization article for more information.

Using Virtual Scrolling

  1. Set the ScrollMode parameter to GridScrollMode.Virtual (the default value is Scrollable).
  2. Set the Height parameter.
  3. Set the RowHeight parameter.
  4. Set the PageSize parameter.

Setting a Value for the Height Parameter

Set the Height parameter to a string value. The value can be in:

  • Pixels—for example, Height="480px".
  • Percent—for example, Height="30%". If you set the Height parameter to a percentage value, ensure that the wrapper of the Grid has a fixed height set in pixels.
  • Relative CSS units like vh, vmin, and vmax—for example, Height="30vh".

The tabs below show how to set the Height parameter with the different value options.

@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<TelerikGrid OnRead="@OnGridRead"
             TItem="@Product"
             ScrollMode="@GridScrollMode.Virtual"
             Height="480px" RowHeight="60" PageSize="20"
             Sortable="true" FilterMode="@GridFilterMode.FilterRow">
    <GridColumns>
        <GridColumn Field="@nameof(Product.Name)" Title="Product Name" />
        <GridColumn Field="@nameof(Product.Stock)" />
    </GridColumns>
</TelerikGrid>

@code {
    private List<Product> GridData { get; set; } = new List<Product>();

    private async Task OnGridRead(GridReadEventArgs args)
    {
        await Task.Delay(200); // simulate network delay

        DataSourceResult result = GridData.ToDataSourceResult(args.Request);

        args.Data = result.Data;
        args.Total = result.Total;
        args.AggregateResults = result.AggregateResults;
    }

    protected override void OnInitialized()
    {
        GridData = new List<Product>();
        var rnd = new Random();

        for (int i = 1; i <= 1000; i++)
        {
            GridData.Add(new Product()
            {
                Id = i,
                Name = $"Product {i}",
                Stock = rnd.Next(0, 100)
            });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public int Stock { get; set; }
    }
}
@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<div style="height: 600px">
    <TelerikGrid OnRead="@OnGridRead"
                 TItem="@Product"
                 ScrollMode="@GridScrollMode.Virtual"
                 Height="100%" RowHeight="60" PageSize="20"
                 Sortable="true" FilterMode="@GridFilterMode.FilterRow">
        <GridColumns>
            <GridColumn Field="@nameof(Product.Name)" Title="Product Name" />
            <GridColumn Field="@nameof(Product.Stock)" />
        </GridColumns>
    </TelerikGrid>
</div>

@code {
    private List<Product> GridData { get; set; } = new List<Product>();

    private async Task OnGridRead(GridReadEventArgs args)
    {
        await Task.Delay(200); // simulate network delay

        DataSourceResult result = GridData.ToDataSourceResult(args.Request);

        args.Data = result.Data;
        args.Total = result.Total;
        args.AggregateResults = result.AggregateResults;
    }

    protected override void OnInitialized()
    {
        GridData = new List<Product>();
        var rnd = new Random();

        for (int i = 1; i <= 1000; i++)
        {
            GridData.Add(new Product()
                {
                    Id = i,
                    Name = $"Product {i}",
                    Stock = rnd.Next(0, 100)
                });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public int Stock { get; set; }
    }
}
@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<div>Select a relative CSS unit from the RadioGroup to see how the Grid's Height reacts to different relative CSS units.</div>

<TelerikRadioGroup Data="@RelativeUnitsOptions"
                   Value="@ChosenRelativeUnit"
                   ValueChanged="@((int relativeUnitId) => RadioGroupChanged(relativeUnitId))"
                   ValueField="@nameof(RelativeUnitsDescriptor.RelativeUnitId)"
                   TextField="@nameof(RelativeUnitsDescriptor.RelativeUnitText)">
</TelerikRadioGroup>

<TelerikGrid OnRead="@OnGridRead"
             TItem="@Product"
             ScrollMode="@GridScrollMode.Virtual"
             Height="@GridHeight" RowHeight="60" PageSize="20"
             Sortable="true" FilterMode="@GridFilterMode.FilterRow">
    <GridColumns>
        <GridColumn Field="@nameof(Product.Name)" Title="Product Name" />
        <GridColumn Field="@nameof(Product.Stock)" />
    </GridColumns>
</TelerikGrid>

@code {
    private int ChosenRelativeUnit { get; set; }

    private string GridHeight { get; set; }

    private void RadioGroupChanged(int relativeUnitId)
    {
        ChosenRelativeUnit = relativeUnitId;

        RelativeUnitsDescriptor relativeUnit = RelativeUnitsOptions.FirstOrDefault(x => x.RelativeUnitId == relativeUnitId);

        GridHeight = "50" + relativeUnit.RelativeUnitText;
    }

    private async Task OnGridRead(GridReadEventArgs args)
    {
        await Task.Delay(200); // simulate network delay

        DataSourceResult result = GridData.ToDataSourceResult(args.Request);

        args.Data = result.Data;
        args.Total = result.Total;
        args.AggregateResults = result.AggregateResults;
    }

    protected override void OnInitialized()
    {
        GridHeight = "50" + RelativeUnitsOptions.FirstOrDefault().RelativeUnitText;

        GridData = new List<Product>();

        var rnd = new Random();

        for (int i = 1; i <= 1000; i++)
        {
            GridData.Add(new Product()
                {
                    Id = i,
                    Name = $"Product {i}",
                    Stock = rnd.Next(0, 100)
                });
        }
    }

    private List<Product> GridData { get; set; } = new List<Product>();

    private List<RelativeUnitsDescriptor> RelativeUnitsOptions { get; set; } = new List<RelativeUnitsDescriptor>
    {
        new RelativeUnitsDescriptor { RelativeUnitId = 1, RelativeUnitText = "vm" },
        new RelativeUnitsDescriptor { RelativeUnitId = 2, RelativeUnitText = "vmax" },
        new RelativeUnitsDescriptor { RelativeUnitId = 3, RelativeUnitText = "vmin" }
    };

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public int Stock { get; set; }
    }

    public class RelativeUnitsDescriptor
    {
        public int RelativeUnitId { get; set; }
        public string RelativeUnitText { get; set; }
    }
}

Setting a Value for the RowHeight Parameter

Set the RowHeight parameter to a decimal value which will always be interpreted as pixels (px). The value of the RowHeight must be greater than the height of the cell (or row) that the browser would normally render.

Consider the following specifics when setting the row height value:

  • The Grid renders padding in the cells by default. The loading skeletons have a line height in order to render. This results in some minimum row heights, which can vary depending on the theme and custom CSS styles on the page.
  • Ensure the height of the td element matches the RowHeight when using the Row Template.
  • Do not change the value of the RowHeight parameter at runtime.
@* Remove the default padding and margin from the cells and remove the default line height of the loading skeletons to reduce the row height. *@

@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<style>
    .small-row-height .k-placeholder-line {
        display: none;
    }

    .small-row-height.k-grid td {
        margin: 0;
        padding: 0;
    }
</style>

<TelerikGrid OnRead="@OnGridRead"
             TItem="@Product"
             ScrollMode="@GridScrollMode.Virtual"
             Height="480px" RowHeight="30" PageSize="20"
             Sortable="true" FilterMode="@GridFilterMode.FilterRow"
             Class="small-row-height">
    <GridColumns>
        <GridColumn Field="@nameof(Product.Name)" Title="Product Name" />
        <GridColumn Field="@nameof(Product.Stock)" />
    </GridColumns>
</TelerikGrid>

@code {
    private List<Product> GridData { get; set; } = new List<Product>();

    private async Task OnGridRead(GridReadEventArgs args)
    {
        await Task.Delay(200); // simulate network delay

        DataSourceResult result = GridData.ToDataSourceResult(args.Request);

        args.Data = result.Data;
        args.Total = result.Total;
        args.AggregateResults = result.AggregateResults;
    }

    protected override void OnInitialized()
    {
        GridData = new List<Product>();
        var rnd = new Random();

        for (int i = 1; i <= 1000; i++)
        {
            GridData.Add(new Product()
                {
                    Id = i,
                    Name = $"Product {i}",
                    Stock = rnd.Next(0, 100)
                });
        }
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public int Stock { get; set; }
    }
}

Setting a Value for the PageSize Parameter

Set the PageSize parameter to an int value. The PageSize determines how many rows are rendered at any given time and how many items are requested from the data source when loading data on demand. For optimal performance, use a page size that fills the grid's data viewport without being excessively large.

Limitations

Virtualization primarily enhances client-side rendering performance and improves the user experience. However, it comes with the trade-off that certain features of the Grid are incompatible with it. An alternative approach is to utilize regular paging combined with manual data source operations to achieve the desired data retrieval performance.

These are the known limitations of the virtual scrolling feature:

See Also

In this article