滚动时逐字切换文本颜色效果实现咨询(参考Polestar Precision区块)
Polestar官网Precision区块文本变色+粘性取消效果实现方案
核心实现原理
这个效果的核心逻辑分为两部分:
- 文本渐变变色:通过双层文本叠加或拆分字母独立控制,结合滚动进度计算,逐步切换文本颜色;
- 粘性定位动态取消:监听滚动位置,当变色动画完成后,移除元素的粘性定位属性。
方案一:双层文本叠加(性能更优,接近官方实现)
无需拆分字母,通过上下两层相同文本叠加,上层白色、下层目标色,滚动时裁剪上层文本实现渐变效果:
- HTML结构
<section class="precision-section"> <div class="sticky-container"> <h2 class="text-layer target-color">Precision</h2> <h2 class="text-layer white-color">Precision</h2> </div> <!-- 区块其他内容 --> </section>
- CSS基础样式
.precision-section { min-height: 200vh; /* 预留足够滚动空间触发动画 */ } .sticky-container { position: sticky; top: 0; height: 100vh; display: flex; align-items: center; justify-content: center; } .text-layer { position: absolute; font-size: 8rem; font-weight: bold; } .target-color { color: rgb(211, 188, 141); } .white-color { color: #fff; clip-path: inset(0 0 0 0); /* 初始完全覆盖下层文本 */ }
- JavaScript逻辑
const stickyContainer = document.querySelector('.sticky-container'); const whiteText = document.querySelector('.white-color'); const section = document.querySelector('.precision-section'); window.addEventListener('scroll', () => { requestAnimationFrame(() => { // 计算当前滚动位置相对于区块的进度(0~1) const sectionTop = section.offsetTop; const sectionHeight = section.offsetHeight; const scrollY = window.scrollY; const progress = Math.min(1, Math.max(0, (scrollY - sectionTop) / (sectionHeight - window.innerHeight))); // 通过clip-path裁剪上层白色文本,逐步露出下层目标色 whiteText.style.clipPath = `inset(0 0 ${progress * 100}% 0)`; // 进度达到1时取消粘性定位 stickyContainer.style.position = progress === 1 ? 'static' : 'sticky'; }); });
方案二:拆分字母独立控制(你尝试的思路)
将每个字母拆分为独立元素,按滚动进度依次切换颜色:
- HTML结构
<section class="precision-section"> <div class="sticky-container"> <h2 class="precision-text">Precision</h2> </div> </section>
- CSS样式
.precision-section { min-height: 200vh; } .sticky-container { position: sticky; top: 0; height: 100vh; display: flex; align-items: center; justify-content: center; } .precision-text span { display: inline-block; font-size: 8rem; font-weight: bold; color: #fff; transition: color 0.3s ease; } .precision-text span.colored { color: rgb(211, 188, 141); }
- JavaScript逻辑
// 自动拆分字母为span元素 const textEl = document.querySelector('.precision-text'); const letters = textEl.textContent.split('').map(letter => { const span = document.createElement('span'); span.textContent = letter; return span; }); textEl.innerHTML = ''; letters.forEach(span => textEl.appendChild(span)); const stickyContainer = document.querySelector('.sticky-container'); const section = document.querySelector('.precision-section'); const totalLetters = letters.length; window.addEventListener('scroll', () => { requestAnimationFrame(() => { const sectionTop = section.offsetTop; const sectionHeight = section.offsetHeight; const scrollY = window.scrollY; const progress = Math.min(1, Math.max(0, (scrollY - sectionTop) / (sectionHeight - window.innerHeight))); // 计算需要变色的字母数量,批量切换类名 const coloredCount = Math.floor(progress * totalLetters); letters.forEach((letter, index) => { letter.classList.toggle('colored', index < coloredCount); }); // 完成变色后取消粘性定位 stickyContainer.style.position = progress === 1 ? 'static' : 'sticky'; }); });
关键细节优化
- 滚动事件用
requestAnimationFrame包裹,避免频繁触发导致的卡顿; - 粘性定位取消也可通过切换类名实现(比如添加
non-sticky类,设置position: static),更符合CSS模块化规范; - 滚动进度计算需确保在目标区块的滚动范围内完成动画,避免超出边界。
内容的提问来源于stack exchange,提问作者Jay Chauhan
相关产品推荐
相关产品推荐

