如何在服务器端 blazor 中捕获 window.onscroll 以模仿 bootstrap 滚动间谍

how to capture window.onscroll in server side blazor to imitate bootstrap scroll spy

我正在构建我的第一个服务器端 Blazor 应用程序。我正在使用 bootstrap css 进行样式设置。我没有使用 bootstrap 脚本。我有一个顶部有固定导航栏的页面。我需要模仿bootstrap滚动间谍。要使用 C# 执行此操作,我需要监视 window.onscroll 以操纵滚动位置并将所需的 css 类 应用到 nav-item

这是我的 HTML:

_Host.cshtml:

@page "/"
@namespace Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Learn Blazor</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/animate/animate.css">
    <link rel="stylesheet" href="~/css/font-awesome/css/all.min.css" />
    <link rel="stylesheet" href="~/css/style.css">
</head>
<body data-spy="scroll" data-target=".site-nav" data-offset="55">
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <script src="_framework/blazor.server.js"></script>
    <script src="~/js//script.js"></script>
</body>
</html>

Index.razor:

@page "/"

<HomeSection></HomeSection>

<ColumnSection></ColumnSection>

<MediaSection></MediaSection>

主页组件:

<header id="page-hero" class="site-header">

    <TopNavMenu></TopNavMenu>

    <section class="layout-hero d-flex align-items-center text-light text-center">
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-11 col-sm-10 col-md-8 animated fadeInUp">
                    <h3 class="page-section-title">Learn Blazor</h3>
                    <p class="page-section-text">
                        My First Blazor App
                    </p>
                </div>
            </div>
        </div>
    </section>

</header>

TopNavMenu 组件:

@inherits TopNavMenuBase

<nav class="site-nav family-sans text-uppercase navbar navbar-expand-md navbar-dark fixed-top" @ref="headerNav">

    <div class="container-fluid">

        <NavLink class="navbar-brand" href="#page-hero" @onclick="NavigateToElementAsync">
            <i class="fas fa-microphone-alt"></i> The Prodcast
        </NavLink>

        <button type="button" class="navbar-toggler @NavToggleCssClass" @onclick="ToggleNavMenu" aria-controls="#myTogglerNav"
                aria-label="Toggle Navigation" aria-expanded="@($"{!collapseNavMenu}".ToLowerInvariant())">
            <span class="navbar-toggler-icon"></span>
        </button>

        <section class="navbar-collapse collapse @NavMenuCssClass" id="myTogglerNav">
            <div class="navbar-nav ml-auto">
                <NavLink class="nav-item nav-link" href="#page-hero" @onclick="NavigateToElementAsync">home</NavLink>
                <NavLink class="nav-item nav-link" href="#page-multicolumn" @onclick="NavigateToElementAsync">columns</NavLink>
                <NavLink class="nav-item nav-link" href="#page-media" @onclick="NavigateToElementAsync">media</NavLink>
               </div>
        </section>

    </div>

</nav>

这是我的组件库:

public class TopNavMenuBase : ComponentBase
{
    protected ElementReference headerNav;
    protected bool collapseNavMenu = true;
    protected string NavToggleCssClass => collapseNavMenu ? "collapsed" : null;
    protected string NavMenuCssClass => !collapseNavMenu ? "show" : null;

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

    [Inject]
    public NavigationManager NavigationManager { get; set; }

    protected void ToggleNavMenu() => collapseNavMenu = !collapseNavMenu;

    protected override async Task OnAfterRenderAsync(bool firstRender) => await NavigateToElementAsync();

    protected async Task NavigateToElementAsync()
    {
        var fragment = new Uri(NavigationManager.Uri).Fragment;
        var elementId = !string.IsNullOrEmpty(fragment) && fragment.StartsWith("#") ? fragment.Substring(1) : fragment;

        await JSRuntime.InvokeAsync<bool>("scrollToElementId", elementId, headerNav);
    }
}

最后是我的脚本:

编辑 1:更新了完整的工作 window.onscroll 以使用纯 javascript

制作滚动间谍
function scrollToElementId(target, headerNav) {
    var topoffset = 55;
    var element = document.getElementById(target);
    if (element) {
        const bodyRect = document.body.getBoundingClientRect().top;
        const elementRect = element.getBoundingClientRect().top;
        const elementPosition = elementRect - bodyRect;
        const offsetPosition = elementPosition - topoffset;

        window.scrollTo({
            top: offsetPosition,
            behavior: 'smooth'
        });

        headerNav.classList.toggle('inbody', target !== 'page-hero');

        return true;
    }

    document.querySelector('a.nav-link[href="#page-hero"]').classList.toggle('active', target === '');

    return false;
}

var sections = {};

document.querySelectorAll(".page-section,.site-header").forEach(section => sections[section.id] = section.offsetTop);

window.onscroll = function () {
    document.querySelector('header nav').classList.toggle('inbody', document.documentElement.scrollTop > 380);
    document.querySelector('#page-media .layout-animation').style['visibility'] = 'hidden';

    var scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;

    Object.keys(sections).forEach(key => {
        if (sections[key] <= scrollPosition) {
            document.querySelectorAll('a.nav-item').forEach(a => a.classList.remove('active'));
            document.querySelector('a[href="#' + key + '"]').classList.add('active');

            if (key === 'page-media') {
                document.querySelector('#page-media .layout-animation').classList.add('animated', 'fadeInRight');
            }
        }
    });
};

以上代码设置有效。但我觉得使用 javascript 捕获 window.onscroll 并不是理想的解决方案。使用服务器端 Blazor,我想用 C# 来完成。但是如何用 @onscroll 捕获这个 window.onscroll 呢?我需要在哪里挂钩@onscrollbody 标签在 _Host.cshtml 中,TopNavMenu 组件嵌套在 Home 组件中,该组件是从 index.razor 文件呈现的。

任何人都可以帮助我如何使它在 Blazor 服务器端正常工作吗?如果我错了,请纠正我。

我终于在 javascript 中使用 IntersectionObserver 实现了它。

我用 highlightMenu() 函数

替换了 window.onscroll 函数,因为它的性能很差
function highlightMenu() {
    const sections = document.querySelectorAll('.page-section,.site-header');
    const config = {
        rootMargin: '-55px 0px -85%'
    };

    let observer = new IntersectionObserver(function
        (entries, self) {

        entries.forEach(entry => {
            if (entry.isIntersecting) {
                intersectionHandler(entry);
            }
        });
    }, config);

    sections.forEach(section => observer.observe(section));
}

function intersectionHandler(entry) {
    const id = entry.target.id;
    document.querySelector('header nav').classList.toggle('inbody', id !== 'page-hero');
    if (id === 'page-media') {
        document.querySelector('#page-media .layout-animation').classList.add('animated', 'fadeInRight');
    }
    const currentlyActive = document.querySelector('nav a.nav-item.active');
    const shouldBeActive = document.querySelector('nav a.nav-item[href="#' + id + '"]');

    if (currentlyActive) {
        currentlyActive.classList.remove('active');
    }
    if (shouldBeActive) {
        shouldBeActive.classList.add('active');
    }
}

然后我在 OnAfterRenderAsync 中从 Index.Razor

调用了这个函数
@code{

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync("highlightMenu");
        }
    }

}

它像 bootstrap 滚动间谍一样工作。希望这对某人有所帮助。