如何让 Intersection Observer 复制 bootstrap 滚动间谍行为

how to make Intersection Observer to replicate bootstrap scroll spy behavior

我正在构建一个 blazor 应用程序,我应该将 java 脚本代码保持在最低限度。所以我在我的视差页面中只使用 bootstrap css。我已经使用 window.onscroll 使类似滚动间谍的行为起作用,因为它现在不能用 c# 完成;但它的性能很差。

谷歌搜索后,我最近遇到了 IntersectionObserver API。所以我想使我现有的 window.onscroll 逻辑与 IntersectionObserver API 一起工作。但是我无法实现它。

这就是我想要做的。在我的带有固定顶部导航栏的视差页面中,当用户滚动到他想查看的相应部分时,我想应用 active css class 到 a.nav-items

这是 HTML:

<body data-spy="scroll" data-target=".site-nav" data-offset="55">

  <header id="page-hero" class="site-header d-flex flex-column align-content-between">

    <nav class="site-nav family-sans navbar navbar-expand-md navbar-dark fixed-top">
      <div class="container-fluid">

        <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#myTogglerNav" aria-controls="myTogglerNav"
          aria-label="Toggle Navigation">
          <span class="navbar-toggler-icon"></span>
        </button>

        <a class="navbar-brand text-uppercase" href="#page-hero">
          <i class="fas fa-cube mr-2"></i> Layout Components</a>

        <div class="collapse navbar-collapse" id="myTogglerNav">
          <div class="navbar-nav ml-auto font-weight-regular text-uppercase">
            <a class="nav-item nav-link" href="#page-hero">home</a>
            <a class="nav-item nav-link" href="#page-multicolumn">columns</a>
            <a class="nav-item nav-link" href="#page-media">media</a>
            <a class="nav-item nav-link" href="#page-photogrid">grid</a>
            <a class="nav-item nav-link" href="#page-carousel">carousel</a>
            <a class="nav-item nav-link" href="#page-nested">nested</a>
            <a class="nav-item nav-link" href="#page-icons">icons</a>
            <a class="nav-item nav-link" href="#page-floater">floater</a>
            <a class="nav-item nav-link" href="#page-cards">cards</a>
          </div>
        </div>

      </div>
    </nav>

    <section class="layout-hero jumbotron jumbotron-fluid d-flex align-items-center mt-5 text-reverse">
      <div class="container text-center">
......
......
......

    <article id="page-multicolumn" class="page-section vertical-padding">

    <header class="page-section-header container">
.....
.....
.....

    <article id="page-media" class="page-section vertical-padding">

    <header class="page-section-header container text-center">
....

我有 article 标签,id 匹配顶部 .nav-itemahref。我只粘贴了几个 html,因为所有文章标签的框架都相同。

这是我现有的 window.onscroll:

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

以上代码有效。但是我想用右边的API.

这是我的 IntersectionObserver:

if (window.IntersectionObserver) {
    let observer = new IntersectionObserver(
        (entries, observer) => {
            console.log(entries);
            entries.forEach(entry => {
                /* Here's where we deal with every intersection */
                document.querySelectorAll('a.nav-item').forEach(a => a.classList.remove('active'));
                if (entry.isIntersecting) {
                    document.querySelector('header nav').classList.toggle('inbody', entry.target.id !== 'page-hero');
                    document.querySelector('#page-media .layout-animation').style['visibility'] = 'hidden';
                    document.querySelector('a[href="#' + entry.target.id + '"]').classList.add('active');
                    if (entry.target.id === 'page-media') {
                        document.querySelector('#page-media .layout-animation').classList.add('animated', 'fadeInRight');
                    }
                }
            });
        }, { root: document.querySelector('.site-nav'), rootMargin: "0px", threshold: 0 });
    document.querySelectorAll('.page-section,.site-header').forEach(section => observer.observe(section));
}

但是我的路口观察器不工作。我厌倦了使用 console.log(entry) 看看会发生什么。所有条目仅在页面加载时记录,之后不工作。请协助我哪里错了。

这里的代码不起作用的原因是 运行 在错误的时间。对 document.querySelectorAll('.page-section,.site-header') 的调用需要在这些元素呈现到 DOM 之后执行,否则它不会匹配任何内容。

这是我如何让它工作的,我稍微调整了功能,如下所示,

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

并使用 Blazor 中的 JS 互操作从我的组件内部调用 OnAfterRenderAsync

@code{

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

}