184 lines
4.9 KiB
Plaintext
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();
|
|
}
|
|
}
|