diff --git a/DeepDrftShared.Client/Components/ParallaxImage.razor b/DeepDrftShared.Client/Components/ParallaxImage.razor index e8aa3b7..8bf1492 100644 --- a/DeepDrftShared.Client/Components/ParallaxImage.razor +++ b/DeepDrftShared.Client/Components/ParallaxImage.razor @@ -5,7 +5,7 @@ role="@(Alt1 != null ? "img" : "presentation")" aria-label="@Alt1" aria-hidden="@(Alt1 == null ? "true" : null)" - style="--window-height: @WindowHeightValue; --parallax-pos: 0%;"> + style="@WindowStyle">
diff --git a/DeepDrftShared.Client/Components/ParallaxImage.razor.cs b/DeepDrftShared.Client/Components/ParallaxImage.razor.cs index 76b9d11..1a844cf 100644 --- a/DeepDrftShared.Client/Components/ParallaxImage.razor.cs +++ b/DeepDrftShared.Client/Components/ParallaxImage.razor.cs @@ -16,6 +16,8 @@ namespace DeepDrftShared.Client.Components; /// container width and image aspect ratio (see ), tracking /// container resizes — this may cause a layout shift on first paint or on orientation change. Pass an explicit /// for above-the-fold hero usage to avoid the shift. +/// When and are provided, height is set +/// via CSS aspect-ratio at render time — no layout shift occurs on any render mode. /// public abstract class ParallaxImageBase : ComponentBase, IAsyncDisposable { @@ -36,6 +38,19 @@ public abstract class ParallaxImageBase : ComponentBase, IAsyncDisposable /// CSS height of the parallax window. When null, renders at 300px then recomputes from container width, image aspect ratio, and . [Parameter] public string? WindowHeight { get; set; } + /// + /// Natural pixel width of . When provided together with + /// , the window height is set via CSS aspect-ratio + /// at render time — eliminating the SSR/hydration layout shift that occurs when + /// dimensions are unknown. Has no effect when is set. + /// + [Parameter] public int? NaturalWidth { get; set; } + + /// + /// Natural pixel height of . See . + /// + [Parameter] public int? NaturalHeight { get; set; } + /// /// Fraction of the aspect-ratio-correct image height to use as the parallax window height. /// Only active when is null. @@ -63,14 +78,33 @@ public abstract class ParallaxImageBase : ComponentBase, IAsyncDisposable [Parameter] public string? Class { get; set; } protected ElementReference WindowRef; - protected string WindowHeightValue { get; private set; } = "300px"; + protected string WindowStyle { get; private set; } = "--window-height: 300px; --parallax-pos: 0%;"; + + private bool AspectRatioMode => + WindowHeight is null && NaturalWidth is > 0 && NaturalHeight is > 0; private IJSObjectReference? _module; private string? _handle; protected override void OnParametersSet() { - WindowHeightValue = WindowHeight ?? "300px"; + if (WindowHeight != null) + { + WindowStyle = $"--window-height: {WindowHeight}; --parallax-pos: 0%;"; + } + else if (AspectRatioMode) + { + // Aspect-ratio mode: height: auto lets aspect-ratio take effect; + // the inline height: auto overrides the CSS --window-height rule. + // aspect-ratio: W / (H * fraction) → window is WindowHeightFraction + // of the image's full display height at container width. + var h = Math.Max(1, (int)Math.Round(NaturalHeight!.Value * WindowHeightFraction)); + WindowStyle = $"height: auto; aspect-ratio: {NaturalWidth!.Value} / {h}; --parallax-pos: 0%;"; + } + else + { + WindowStyle = "--window-height: 300px; --parallax-pos: 0%;"; + } } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -90,7 +124,11 @@ public abstract class ParallaxImageBase : ComponentBase, IAsyncDisposable { speed = ParallaxSpeed, invertDirection = InvertDirection, - heightFraction = WindowHeight == null ? (double?)WindowHeightFraction : null, + // heightFraction is null in explicit-height and aspect-ratio modes; + // JS auto-height only runs in the fallback (no dimensions provided). + heightFraction = (WindowHeight == null && !AspectRatioMode) + ? (double?)WindowHeightFraction + : null, image1 = Image1 }); }