AyCode.Blazor/AyCode.Blazor.Components/Components/MgLazyLoadContent.razor

184 lines
4.9 KiB
Plaintext

@using Microsoft.JSInterop
@inject IJSRuntime JS
<div @ref="_containerRef" class="@ContainerCssClass" style="@ContainerStyle">
@if (IsVisible || ForceRender)
{
@ChildContent
}
else if (PlaceholderContent != null)
{
@PlaceholderContent
}
else
{
<div class="lazy-content-placeholder" style="min-height: @MinHeight;">
@if (ShowLoadingIndicator)
{
<div class="text-center py-3">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Betöltés...</span>
</div>
</div>
}
</div>
}
</div>
@code {
private ElementReference _containerRef;
private DotNetObjectReference<MgLazyLoadContent>? _dotNetRef;
private bool _isObserverInitialized;
/// <summary>
/// Content to render when visible
/// </summary>
[Parameter, EditorRequired]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Optional placeholder content to show before the element becomes visible
/// </summary>
[Parameter]
public RenderFragment? PlaceholderContent { get; set; }
/// <summary>
/// Root margin for IntersectionObserver (e.g., "100px" to load 100px before visible)
/// </summary>
[Parameter]
public string RootMargin { get; set; } = "50px";
/// <summary>
/// Threshold for IntersectionObserver (0.0 to 1.0)
/// </summary>
[Parameter]
public double Threshold { get; set; } = 0.01;
/// <summary>
/// Minimum height for the placeholder (prevents layout shift)
/// </summary>
[Parameter]
public string MinHeight { get; set; } = "100px";
/// <summary>
/// CSS class for the container
/// </summary>
[Parameter]
public string? ContainerCssClass { get; set; }
/// <summary>
/// Inline style for the container
/// </summary>
[Parameter]
public string? ContainerStyle { get; set; }
/// <summary>
/// Force render regardless of visibility (useful for disabling lazy loading)
/// </summary>
[Parameter]
public bool ForceRender { get; set; }
/// <summary>
/// Show a loading spinner in the placeholder
/// </summary>
[Parameter]
public bool ShowLoadingIndicator { get; set; } = true;
/// <summary>
/// Callback when content becomes visible
/// </summary>
[Parameter]
public EventCallback OnContentVisible { get; set; }
/// <summary>
/// Gets whether the content is currently visible
/// </summary>
public bool IsVisible { get; private set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !ForceRender)
{
await InitializeObserverAsync();
}
}
private async Task InitializeObserverAsync()
{
if (_isObserverInitialized) return;
try
{
_dotNetRef = DotNetObjectReference.Create(this);
// Initialize observer and check immediate visibility
var isCurrentlyVisible = await JS.InvokeAsync<bool>(
"lazyContentObserver.observe",
_containerRef,
_dotNetRef,
RootMargin,
Threshold);
_isObserverInitialized = true;
// If already visible, trigger the callback immediately
if (isCurrentlyVisible && !IsVisible)
{
await OnVisibilityChanged(true);
}
}
catch (JSException ex)
{
Console.WriteLine($"MgLazyLoadContent: Failed to initialize observer: {ex.Message}");
// Fallback: render immediately if JS fails
IsVisible = true;
await OnContentVisible.InvokeAsync();
StateHasChanged();
}
}
[JSInvokable]
public async Task OnVisibilityChanged(bool isVisible)
{
if (IsVisible == isVisible) return;
IsVisible = isVisible;
if (IsVisible)
{
await OnContentVisible.InvokeAsync();
}
StateHasChanged();
}
/// <summary>
/// Manually triggers the OnContentVisible callback if the content is currently visible.
/// Useful when the content data changes but visibility hasn't changed.
/// </summary>
public async Task TriggerContentVisibleAsync()
{
if (IsVisible)
{
await OnContentVisible.InvokeAsync();
}
}
public async ValueTask DisposeAsync()
{
if (_isObserverInitialized)
{
try
{
await JS.InvokeVoidAsync("lazyContentObserver.unobserve", _containerRef);
}
catch
{
// Ignore errors during disposal
}
}
_dotNetRef?.Dispose();
}
}