如何用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>
关键说明
- 多观测器分离逻辑:为头部、居中导航、
.page-name分别创建Intersection Observer,通过rootMargin精准控制每个元素的触发时机 - 动态触发点计算:
- 头部:保持原逻辑,区块触达头部下方时切换
- 居中导航:
rootMargin设为视口垂直中心,区块中点到达此处时切换 .page-name:根据元素所在的左侧垂直区域调整rootMargin,实现提前变色
- 方向判断保留:继续使用滚动方向判断,确保上下滚动时的主题切换逻辑一致
内容的提问来源于stack exchange,提问作者user1406440
相关产品推荐
相关产品推荐

