You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor滚动加载异常:文本正常,添加图片组件后仅加载一次

问题描述

在Blazor项目中实现滚动加载功能时出现异常:

  • 仅加载文本列表时,滚动到底部可无限触发加载新文本
  • 添加图片组件(FunnyLayout)后,仅能触发一次加载,后续判断滚动到底部的布尔值始终为false

相关代码

host.cshtml中的脚本

<script>
    function isScrollAtBottom(container) {
        return container.scrollHeight - container.scrollTop === container.clientHeight;
    }
</script>

页面组件代码

<div @ref="scrollContainer" @onscroll="HandleScroll" style="overflow:scroll; height: 100vh;">

    @foreach (var item in FC)
    {
        <FunnyLayout ImageId="@item.ImageId" ImageTitle="@item.ImageTitle" />
        <div class="spacer"></div>
    }

    @foreach (var item in items)
    {
        <p>@item</p>
    }

</div>

<style>
    .spacer{
        height: 40px;
    }
</style>

@code {
    private List<string> items = new List<string>
    {
        "Item 1", "Item 2", "Item 3"
    };

    private List<FunnyContent> FC = new List<FunnyContent>
    {
        new FunnyContent{ImageId = "gif", ImageTitle = "il neggtt"},
        new FunnyContent{ImageId = "1", ImageTitle = "was geht"},
        new FunnyContent{ImageId = "2", ImageTitle = "ma boy"},
    };

    private ElementReference scrollContainer;

    [Inject]
    private IJSRuntime JSRuntime { get; set; }

    protected override async Task OnInitializedAsync()
    {

    }

    private async Task HandleScroll()
    {
        var isAtBottom = await JSRuntime.InvokeAsync<bool>("isScrollAtBottom", scrollContainer);
        if (isAtBottom)
        {
            LoadMoreContent();
        }
    }

    private void LoadMoreContent()
    {
        items.Add("New Item");
        items.Add("Another New Item");

        var x = new FunnyContent { ImageId = "1", ImageTitle = "himom0" };
        FC.Add(x);
        FC.Add(x);
        FC.Add(x);
    }

    private class FunnyContent
    {
        public string ImageId { get; set; }
        public string ImageTitle { get; set; }
    }
}

原因分析

核心问题出在图片加载的异步性和严格的滚动判断逻辑:

  1. 文本内容是即时渲染的,添加新文本后DOM立刻更新,scrollHeight同步变大,此时滚动位置刚好处于底部,严格相等的判断条件成立,能继续触发加载。
  2. 图片组件中的图片是异步加载的:调用LoadMoreContent添加图片组件后,Blazor先渲染图片占位DOM,但图片实际还在加载中,此时scrollHeight是占位高度。等图片加载完成后,图片容器高度变大,scrollHeight突然增加,但滚动位置仍停留在之前的位置,导致scrollHeight - scrollTop大于clientHeight,严格相等的条件不再满足,后续滚动判断始终返回false。

解决方案

1. 放宽滚动到底部的判断条件

把严格相等改为允许微小误差,避免因图片加载导致的scrollHeight变化影响判断:

function isScrollAtBottom(container) {
    // 允许10px以内的误差,视为已到达底部
    return container.scrollHeight - container.scrollTop <= container.clientHeight + 10;
}

2. 监听图片加载完成事件

在FunnyLayout组件中给图片添加onload事件,当图片加载完成后通知父组件重新检查滚动状态:

  • FunnyLayout.razor中:
<img src="@GetImageUrl(ImageId)" alt="@ImageTitle" @onload="NotifyImageLoaded" />

@code {
    [Parameter] public string ImageId { get; set; }
    [Parameter] public string ImageTitle { get; set; }
    [Parameter] public EventCallback OnImageLoaded { get; set; }

    private string GetImageUrl(string id) {
        // 返回图片资源路径
        return $"/images/{id}.jpg";
    }

    private async Task NotifyImageLoaded() {
        await OnImageLoaded.InvokeAsync();
    }
}
  • 父组件修改调用:
<FunnyLayout ImageId="@item.ImageId" ImageTitle="@item.ImageTitle" OnImageLoaded="CheckScrollPosition" />
  • 父组件添加检查方法:
private async Task CheckScrollPosition() {
    var isAtBottom = await JSRuntime.InvokeAsync<bool>("isScrollAtBottom", scrollContainer);
    if (isAtBottom) {
        LoadMoreContent();
    }
}

3. 优化滚动事件触发频率(可选)

添加节流函数减少滚动事件的触发次数,避免频繁调用JS互操作:

let scrollTimeout;
function handleScrollThrottled(container, callback) {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(() => {
        const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 10;
        callback(isAtBottom);
    }, 100);
}

内容的提问来源于stack exchange,提问作者innom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:32:13