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

如何用IntersectionObserver实现元素在深色区域后自动变色

问题解决:固定元素随对应区块提前变色

核心思路

当前代码仅在区块触达页面顶部时切换头部主题,要实现居中prev/next和.page-name提前变色,需将观测触发点从页面顶部调整到目标固定元素的垂直位置:

  • 对居中的page-prev/page-next,触发点设为视口垂直中心
  • 对.page-name,触发点设为其所在的垂直区域位置
  • 保留原头部的顶部触发逻辑,实现不同元素的差异化触发时机

修改后的代码

JavaScript

const header = document.querySelector('[data-header]');
const sections = [...document.querySelectorAll('[data-section]')];
const scrollRoot = document.querySelector('[data-scroller]');
const pageNavs = [...document.querySelectorAll('.page-nav')];
const pageName = document.querySelector('.page-name');

let prevYPosition = 0;
let direction = 'up';

// 头部观测配置:区块触达头部下方时切换
const headerObserverOptions = {
    root: scrollRoot,
    rootMargin: `${header.offsetHeight * -1}px 0px 0px 0px`,
    threshold: 0
};

// 居中导航观测配置:区块垂直中点到达视口中心时切换
const navObserverOptions = {
    root: scrollRoot,
    rootMargin: `calc(-50vh + ${header.offsetHeight}px) 0px calc(-50vh)px 0px`,
    threshold: 0
};

// .page-name观测配置:区块对应位置到达元素垂直区域时切换
const pageNameObserverOptions = {
    root: scrollRoot,
    rootMargin: `calc(-25vh + ${header.offsetHeight}px) 0px calc(-75vh)px 0px`,
    threshold: 0
};

const getTargetSection = (entry) => {
    const index = sections.findIndex(section => section === entry.target);
    return direction === 'down' && index < sections.length - 1 ? sections[index + 1] : entry.target;
};

// 分元素更新主题
const updateHeaderTheme = (target) => {
    header.setAttribute('data-theme', target.dataset.section);
};

const updateNavTheme = (target) => {
    pageNavs.forEach(nav => nav.setAttribute('data-theme', target.dataset.section));
};

const updatePageNameTheme = (target) => {
    pageName.setAttribute('data-theme', target.dataset.section);
};

const shouldUpdate = (entry) => {
    if (direction === 'down' && !entry.isIntersecting) return true;
    if (direction === 'up' && entry.isIntersecting) return true;
    return false;
};

const createObserverCallback = (updateFn) => {
    return (entries, observer) => {
        entries.forEach(entry => {
            direction = scrollRoot.scrollTop > prevYPosition ? 'down' : 'up';
            prevYPosition = scrollRoot.scrollTop;
            
            const target = getTargetSection(entry);
            if (shouldUpdate(entry)) {
                updateFn(target);
            }
        });
    };
};

// 创建三个独立观测器,分别处理不同元素
const headerObserver = new IntersectionObserver(createObserverCallback(updateHeaderTheme), headerObserverOptions);
const navObserver = new IntersectionObserver(createObserverCallback(updateNavTheme), navObserverOptions);
const pageNameObserver = new IntersectionObserver(createObserverCallback(updatePageNameTheme), pageNameObserverOptions);

// 每个区块被三个观测器监听
sections.forEach(section => {
    headerObserver.observe(section);
    navObserver.observe(section);
    pageNameObserver.observe(section);
});

CSS

/* Body */
body {
  margin: 0;
  padding: 0;
}

/* Variables */
:root {
    --dark: #fff;
    --light: #000;
}

/* Header */
.header {
  padding: 16px 0;
  position: fixed;
  top: 0;
  width: 100%;
}

/* Nav */
.nav-main {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-main__item {
  margin-left: 32px;
}

.nav-main__link {
  color: currentcolor;
}

/* Name Page */
.page-name {
  color: currentcolor;
  display: block;
  rotate: -180deg;
  margin: 80px 0 32px 0;
  overflow: hidden;
  position: absolute;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
  writing-mode: vertical-rl;
  max-height: calc(50vh - 128px);
}

/* Grid Overlay */
.grid--faux {
  display: flex;
  justify-content: space-between;
  height: 100vh;
  pointer-events: none;
  padding: 0 32px;
  position: fixed;
  width: calc(100vw - 64px);
  z-index: 10;
}

.page-prev,
.page-next {
  color: currentcolor;
  display: flex;
  align-items: center;
  align-self: center;
  pointer-events: auto;
}

/* Main Content */
.section {
  background: #eee;
  color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  width: 100vw;
}

.section--dark {
  background: #444;
  color: white;
}

[data-theme="light"] {
    color: var(--light);
}

[data-theme="dark"]  {
    color: var(--dark);
}

.scroller {
    height: 100vh;
    width: 100%;
    overflow: auto;
}

HTML

<div class="header" data-header>
  <ul class="nav-main">
    <li class="nav-main__item"><a href="#" class="nav-main__link" data-link>Home</a></li>
    <li class="nav-main__item"><a href="#" class="nav-main__link" data-link>Work</a></li>
    <li class="nav-main__item"><a href="#" class="nav-main__link" data-link>Contact</a></li>
  </ul>
</div>

<div class="grid--faux">
  <p class="page-name" data-page>Name of template</p>
  <a href="#" class="page-nav page-prev" data-nav>Prev</a>
  <a href="#" class="page-nav page-next" data-nav>Next</a>
</div>

<div class="main scroller" data-scroller>
  <section class="section section--dark" data-section="dark">
    Fixed text should display white when over this section.
  </section>
  <section class="section" data-section="light">
    Fixed text should display black when over this section.
  </section>
  <section class="section section--dark" data-section="dark">
    Fixed text should display white when over this section.
  </section>
  <section class="section" data-section="light">
    Fixed text should display black when over this section.
  </section>
</div>

关键说明

  1. 多观测器分离逻辑:为头部、居中导航、.page-name分别创建Intersection Observer,通过rootMargin精准控制每个元素的触发时机
  2. 动态触发点计算:
    • 头部:保持原逻辑,区块触达头部下方时切换
    • 居中导航:rootMargin设为视口垂直中心,区块中点到达此处时切换
    • .page-name:根据元素所在的左侧垂直区域调整rootMargin,实现提前变色
  3. 方向判断保留:继续使用滚动方向判断,确保上下滚动时的主题切换逻辑一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 06:37:02