如何在服务器端 Blazor 中捕获 window.onscroll,以模仿引导程序滚动间谍



我正在构建我的第一个服务器端 blazor 应用程序。我正在使用引导 css 进行样式设置。我没有使用引导脚本。我有一个顶部带有固定导航栏的页面。我需要模仿引导滚动间谍。要使用 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>

顶部导航菜单组件:

@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呢?我需要在哪里挂这个@onscroll.body标记位于_Host.cshtmlTopNavMenu组件嵌套Home组件中,该组件是从index.razor文件呈现的组件。

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

我终于在javascript中使用IntersectionObserver来工作。

我用window.onscroll函数替换了它的性能highlightMenu()因为它的性能令人讨厌

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');
}
}

然后我从Index.RazorOnAfterRenderAsync内部调用了这个函数,

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

它就像一个引导滚动间谍。希望这对某人有所帮助。