ResizeObserver
React to element size changes the moment they happen: ObserveResize is an extension method on any ElementReference that reports content, border and device-pixel box sizes to a C# callback - the right tool for container queries, canvas sizing and responsive components, without window-resize polling.
Capture the element with the ref directive and call ObserveResize with the IJSRuntime and a handler. The browser reports an initial entry immediately, then again on every size change. The box below has a CSS resize handle in its bottom-right corner - start observing, then drag it.
IJSRuntime js
<div @ref="box" style="resize: both; overflow: auto;">Drag my corner</div>
{
private ElementReference box;
private ButilSubscription? subscription;
private async Task Start()
{
subscription = await box.ObserveResize(js, entries =>
{
foreach (var entry in entries)
{
Console.WriteLine($"{entry.InlineSize} x {entry.BlockSize}");
}
});
}
}The box parameter selects which box dimensions trigger the observer: ContentBox (the default) excludes padding and borders, BorderBox includes them, and DevicePixelContentBox measures in physical device pixels - the choice that matters when sizing a canvas backing store. Pick one below and re-observe.
await box.ObserveResize(js, entries =>
{
// fires when the border box changes size
}, ResizeObserverBox.BorderBox);Each entry flattens the browser's ResizeObserverEntry into the fields layout work actually needs: the content rect, the logical inline and block sizes of the observed box, and the device-pixel sizes for pixel-perfect rendering.
await box.ObserveResize(js, entries =>
{
var entry = entries[^1];
Rect? rect = entry.ContentRect; // content box geometry
var inline = entry.InlineSize; // width in horizontal writing mode
var block = entry.BlockSize; // height in horizontal writing mode
var dpInline = entry.DevicePixelInlineSize;
var dpBlock = entry.DevicePixelBlockSize;
});The observer does not care who changed the size: user drags, CSS state changes, Blazor re-renders and style writes all produce entries. Here the button widens the box through the Element extension SetAttribute while the observer reports each step.
// any style write triggers the observer just like a user drag:
await box.SetAttribute("style", "width: 20rem; height: 6rem; resize: both; overflow: auto;");ObserveResize returns a ButilSubscription; disposing it unobserves the element and releases the interop reference. Dispose every observer you start - at the latest in your component's DisposeAsync, as this page does.
IAsyncDisposable
private ButilSubscription? subscription;
public async ValueTask DisposeAsync()
{
if (subscription is not null)
{
await subscription.DisposeAsync();
}
}API reference
Task<ButilSubscription> ObserveResize(IJSRuntime js, Action<ResizeObserverEntry[]> handler, ResizeObserverBox box = ResizeObserverBox.ContentBox)enum ResizeObserverBox { ContentBox, BorderBox, DevicePixelContentBox }Rect? ContentRect { get; set; }double InlineSize { get; set; }double BlockSize { get; set; }double DevicePixelInlineSize { get; set; }double DevicePixelBlockSize { get; set; }Guid Id { get; }ValueTask DisposeAsync()