Blazor中JavaScript IntersectionObserver事件触发两次的原因及解决
问题描述
在Blazor应用中使用JavaScript的IntersectionObserver实现滚动列表的分页懒加载,已通过断点确认监听器仅订阅一次,但每次触发时OnIntersectionSearch()都会执行两次。想明确事件触发两次的原因,以及解决方法。
相关代码
JavaScript代码
window.ObserverSearch = { observer: null, Initialize: function (component, observerId) { this.observer = new IntersectionObserver(e => { component.invokeMethodAsync('OnIntersectionSearch'); }); let element = document.getElementById(observerId); if (element == null) throw new Error("Target was not found"); this.observer.observe(element); } };
Blazor组件(InfiniteScrollSearch.razor)
@inject IJSRuntime JSRuntime @ChildContent
Blazor组件代码文件(InfiniteScrollSearch.razor.cs)
public partial class InfiniteScrollSearch { [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public string? ObserverSearchTarget { get; set; } [Parameter] public EventCallback<bool> ObservableSearchEndReached { get; set; } private DotNetObjectReference<InfiniteScrollSearch>? _objectRef; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _objectRef = DotNetObjectReference.Create(this); await JSRuntime.InvokeAsync<dynamic>("ObserverSearch.Initialize", _objectRef, ObserverSearchTarget); } } [JSInvokable] public async Task OnIntersectionSearch() { // 此处总是被调用两次 await ObservableSearchEndReached.InvokeAsync(true); } }
原因分析
IntersectionObserver的回调默认会在两种场景触发:
- 目标元素进入视口时
- 目标元素离开视口时
同时,默认配置下,只要元素有部分进入视口就会触发回调,这就是你遇到两次调用的核心原因。
解决方案
1. 过滤交叉状态,仅处理进入视口事件
在JavaScript回调中检查交叉条目isIntersecting属性,仅当元素进入视口时调用Blazor方法:
window.ObserverSearch = { observer: null, Initialize: function (component, observerId) { this.observer = new IntersectionObserver(entries => { const targetEntry = entries[0]; // 仅当元素进入视口时触发 if (targetEntry.isIntersecting) { component.invokeMethodAsync('OnIntersectionSearch'); } }); let element = document.getElementById(observerId); if (element == null) throw new Error("Target was not found"); this.observer.observe(element); } };
2. 配置观察选项,精准控制触发时机
可以给IntersectionObserver添加配置参数,比如设置threshold(触发阈值)或rootMargin(提前触发距离):
this.observer = new IntersectionObserver(entries => { const targetEntry = entries[0]; if (targetEntry.isIntersecting) { component.invokeMethodAsync('OnIntersectionSearch'); } }, { threshold: 1.0, // 元素完全进入视口才触发 rootMargin: '0px 0px 100px 0px' // 可选:元素距离底部还有100px时就触发,提前加载数据 });
3. 添加加载锁,避免重复触发(可选)
在Blazor方法中添加加载状态锁,防止短时间内重复调用:
private bool _isLoading = false; [JSInvokable] public async Task OnIntersectionSearch() { if (_isLoading) return; _isLoading = true; try { await ObservableSearchEndReached.InvokeAsync(true); } finally { _isLoading = false; } }
内容的提问来源于stack exchange,提问作者Frank Mehlhop
相关产品推荐
相关产品推荐

