逐字滚动变色效果实现教程:精准控制文本颜色渐变与滚动锁定

1次阅读

逐字滚动变色效果实现教程:精准控制文本颜色渐变与滚动锁定

本文详解如何通过原生 javascript 和 css 实现“滚动时逐字改变文本颜色”的交互动效(如 polestar 官网 precision 区块),包含字符拆分、滚动监听、时间节流、视觉过渡及自动解锁逻辑。

本文详解如何通过原生 javascript 和 css 实现“滚动时逐字改变文本颜色”的交互动效(如 polestar 官网 precision 区块),包含字符拆分、滚动监听、时间节流、视觉过渡及自动解锁逻辑。

在高端品牌官网或创意型落地页中,一种极具表现力的动效是:当用户向下滚动至特定区域时,一段文字中的每个字符按顺序从默认色渐变为目标色(例如 #ffffff → #d3bc8d),同时页面在该区域短暂“锁定”滚动,确保用户注意力聚焦于文字变化过程;待所有字符完成着色后,滚动恢复自由。这种效果看似复杂,实则可通过轻量级原生代码精准实现——无需依赖 ScrollMagic 或 GSAP 等重型库。

核心实现思路

整个流程分为三个关键阶段:

  • 预处理阶段:将带有 .stickyColor 类的文本节点(如

    )内部文本拆解为独立 元素,每个 span 包裹一个字符;

  • 滚动监听阶段:监听 scroll 事件,检测首个进入视口临界区(如距顶部 100px)的 .stickyColor 元素,并触发“锁定”状态;
  • 逐帧着色阶段:在锁定状态下,以最小时间间隔(如 50ms)依次为未着色的 添加 newColor 类,利用 CSS 过渡实现平滑变色;全部完成后自动解除锁定。

完整可运行代码示例

以下为精简、可直接复用的三段式实现(HTML + CSS + js),已做生产级优化:

<!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8" />   <meta name="viewport" content="width=device-width, initial-scale=1.0"/>   <title>Letter-by-Letter Scroll Color Effect</title>   <style>     body {       background-color: #000;       margin: 0;       color: #fff;       font-family: 'Verdana', sans-serif;       line-height: 1.6;     }     .stickyColor {       margin: 200px 0;       font-size: 1.8rem;       position: relative;       transition: opacity 0.3s ease;     }     .stickyColor span {       display: inline-block;       color: #fff;       transition: color 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);     }     .stickyColor span.newColor {       color: #d3bc8d; /* Polestar 风格暖金 */     }     .stickyColor.locked {       opacity: 0.95;     }     .big {       height: 120vh;       background: linear-gradient(to bottom, #000 0%, #111 100%);     }   </style> </head> <body>   <p style="padding: 40px 20px; font-size: 1.1rem;">↓ 滚动查看逐字变色效果 ↓</p>   <p class="stickyColor">Precision engineered.</p>   <p class="stickyColor">Every detail matters.</p>   <div class="big"></div>   <script>     // 配置项(可按需调整)     const STICKY_TOP_OFFSET = 120;        // 触发粘性锁定的视口顶部距离(px)     const MIN_COLOR_INTERVAL_MS = 40;    // 字符着色最小时间间隔(ms)      // 禁用浏览器滚动位置记忆,避免回退时干扰效果     history.scrollRestoration = 'manual';      // 状态变量     let lockedAt = null;           // 锁定时的 scrollY 值     let lockedSticky = null;       // 当前被锁定的 dom 元素     let lastColorTime = 0;         // 上次着色时间戳      // 【步骤1】预处理:将每个 .stickyColor 文本拆分为单字符 span     document.querySelectorAll('.stickyColor').forEach(el => {       const text = el.textContent.trim();       el.innerHTML = '';       text.split('').forEach(char => {         const span = document.createElement('span');         span.textContent = char;         el.appendChild(span);       });     });      // 【步骤2】核心滚动监听逻辑     window.addEventListener('scroll', () => {       const currentScroll = window.scrollY;        // 若已锁定,仅处理着色 & 滚动抑制       if (lockedAt !== null) {         // 条件1:向下滚动且超过锁定位置         // 条件2:满足最小着色时间间隔         if (currentScroll > lockedAt &&              (!lastColorTime || date.now() - lastColorTime >= MIN_COLOR_INTERVAL_MS)) {            // 查找第一个未着色的 span           const spans = lockedSticky.querySelectorAll('span');           const nextSpan = Array.from(spans).find(span => !span.classList.contains('newColor'));            if (nextSpan) {             nextSpan.classList.add('newColor');             lastColorTime = Date.now();           } else {             // 所有字符已着色 → 解锁             lockedAt = null;             lockedSticky = null;             lastColorTime = 0;           }         }          // 强制保持在锁定位置(防用户快速滚动跳过效果)         if (currentScroll > lockedAt) {           window.scrollTo(0, lockedAt);         }         return;       }        // 【步骤3】若未锁定,查找首个应触发锁定的元素       document.querySelectorAll('.stickyColor').forEach(el => {         if (el.classList.contains('locked')) return;          const rect = el.getBoundingClientRect();         if (rect.top < STICKY_TOP_OFFSET && rect.bottom > 0) {           lockedAt = currentScroll;           lockedSticky = el;           el.classList.add('locked');         }       });     });   </script> </body> </html>

关键注意事项与最佳实践

  • DOM 就绪时机:脚本必须置于 底部(或使用 DOMContentLoaded),确保所有 .stickyColor 元素已挂载,否则字符拆分将失败;
  • ⚠️ 性能优化:scroll 事件高频触发,本方案通过 lockedAt 状态提前退出非必要逻辑,并结合 Date.now() 节流着色操作,避免重排重绘压力;
  • ? 视觉增强建议
    • 使用 cubic-bezier(0.34, 1.56, 0.64, 1) 等缓动函数让颜色过渡更富弹性;
    • 为 .stickyColor 添加轻微 opacity: 0.95(锁定时)可强化“聚焦感”;
    • 多段文本建议错开触发位置(如第二段设 STICKY_TOP_OFFSET = 180),避免叠锁定;
  • ? 无障碍考量:该效果属纯装饰性动效,不影响内容可读性;若需兼容高对比度模式或减少动画偏好用户,可封装为 @media (prefers-reduced-motion: reduce) 下降级为无动画版本。

掌握此模式后,你不仅能复现 Polestar 的经典文案动效,还可灵活拓展至数字计数器逐位点亮、密码输入逐字符高亮、甚至 SVG 路径描边动画等场景——本质都是「基于滚动进度驱动的序列化 DOM 状态更新」。动手尝试,让文字在用户指尖下真正“活”起来。

text=ZqhQzanResources