using System.Text.Json; using System.Threading.Tasks; using AntDesign.JsInterop; using Microsoft.AspNetCore.Components; namespace AntDesign { public partial class Affix : AntDomComponentBase { private const string PrefixCls = "ant-affix"; private const string RootScollSelector = "window"; private bool _affixed; private bool _rootListened; private bool _targetListened; private bool Affixed { get => _affixed; set { if (_affixed != value) { _affixed = value; StateHasChanged(); if (OnChange.HasDelegate) { OnChange.InvokeAsync(_affixed); } } } } private ElementReference _childRef; private string _hiddenStyle; private string _affixStyle; [Inject] private DomEventService DomEventService { get; set; } #region Parameters /// /// Offset from the bottom of the viewport (in pixels) /// [Parameter] public uint? OffsetBottom { get; set; } /// /// Offset from the top of the viewport (in pixels) /// [Parameter] public uint? OffsetTop { get; set; } = 0; [Parameter] public string TargetSelector { get; set; } [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public EventCallback OnChange { get; set; } #endregion Parameters protected override void OnInitialized() { base.OnInitialized(); SetClasses(); } public async override Task SetParametersAsync(ParameterView parameters) { await base.SetParametersAsync(parameters); } protected async override Task OnFirstAfterRenderAsync() { await base.OnFirstAfterRenderAsync(); var domRect = await JsInvokeAsync(JSInteropConstants.GetBoundingClientRect, _childRef); _hiddenStyle = $"width: {domRect.width}px; height: {domRect.height}px;"; DomEventService.AddEventListener(RootScollSelector, "scroll", OnWindowScroll, false); DomEventService.AddEventListener(RootScollSelector, "resize", OnWindowResize, false); await RenderAffixAsync(); if (!_rootListened && string.IsNullOrEmpty(TargetSelector)) { _rootListened = true; } else if (!string.IsNullOrEmpty(TargetSelector)) { DomEventService.AddEventListener(TargetSelector, "scroll", OnTargetScroll); DomEventService.AddEventListener(TargetSelector, "resize", OnTargetResize); _targetListened = true; } } private async void OnWindowScroll(JsonElement obj) { await RenderAffixAsync(true); } private async void OnWindowResize(JsonElement obj) { await RenderAffixAsync(true); } private async void OnTargetScroll(JsonElement obj) { await RenderAffixAsync(); } private async void OnTargetResize(JsonElement obj) { await RenderAffixAsync(); } private void SetClasses() { ClassMapper.Clear() .If(PrefixCls, () => _affixed); } private async Task RenderAffixAsync(bool windowscrolled = false) { if (windowscrolled && !string.IsNullOrEmpty(TargetSelector)) { if (!Affixed) { return; } _affixStyle = string.Empty; Affixed = false; StateHasChanged(); return; } DomRect childRect = null; DomRect domRect = null; Window window = null; async Task GetWindow() { window = await JsInvokeAsync(JSInteropConstants.GetWindow); } async Task GetDomReact() { domRect = await JsInvokeAsync(JSInteropConstants.GetBoundingClientRect, Ref); } async Task GetChildReact() { childRect = await JsInvokeAsync(JSInteropConstants.GetBoundingClientRect, _childRef); } await Task.WhenAll(new[] { GetWindow(), GetDomReact(), GetChildReact() }); if (childRect == null || domRect == null || window == null) { return; } _hiddenStyle = $"width: {childRect.width}px; height: {childRect.height}px;"; DomRect containerRect; if (string.IsNullOrEmpty(TargetSelector)) { containerRect = new DomRect() { top = 0, bottom = window.innerHeight, height = window.innerHeight, }; } else { containerRect = await JsInvokeAsync(JSInteropConstants.GetBoundingClientRect, TargetSelector); } // become affixed if (OffsetBottom.HasValue) { // domRect.bottom / domRect.top have the identical value here. var bottom = containerRect.bottom - OffsetBottom; if (domRect.bottom > bottom) { _affixStyle = _hiddenStyle + $"bottom: { window.innerHeight - bottom}px; position: fixed;"; Affixed = true; } else { _affixStyle = string.Empty; Affixed = false; } } else if (OffsetTop.HasValue) { var top = containerRect.top + OffsetTop; if (domRect.top < top && top > 0) { _affixStyle = _hiddenStyle + $"top: {top}px; position: fixed;"; Affixed = true; } else { _affixStyle = string.Empty; Affixed = false; } } StateHasChanged(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); DomEventService.RemoveEventListerner(RootScollSelector, "scroll", OnWindowScroll); DomEventService.RemoveEventListerner(RootScollSelector, "resize", OnWindowResize); } } }