You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于滚动的文本颜色动画:如何实现逐行而非整块的动画效果?

实现逐行顺序的滚动文本填充动画

我完全懂你想要的那种效果——就像SXSW网站里的那样,滚动时文本逐行依次从左到右填充变色,上一行完全完成后才启动下一行。你的现有代码已经完成了文本拆分行的基础工作,问题出在滚动进度的计算逻辑上,咱们来调整一下:

核心问题分析

你之前的进度计算是把整个滚动范围平均分配给所有行,导致多个行可能同时处于动画状态。我们需要把滚动过程拆分成独立的行阶段:整个滚动触发区间被分成N个相等的部分(N是行数),每个阶段只处理对应行的动画,前一个阶段完成后才进入下一个。

修改后的完整代码

HTML

<section style="height: 150vh; display: flex; align-items: center; justify-content: center; padding: 2rem;">
  <blockquote class="quote-text" style="max-width: 800px; font-size: 1.5rem; line-height: 1.8;">
    Community banks play a critical role in expanding access to financial services and supporting the economic vitality of the communities they serve. Ongoing education for directors and senior management strengthens board oversight and promotes effective governance.
  </blockquote>
</section>

CSS

.quote-text .line {
  display: block;
  --progress: 0%;
  background: linear-gradient(
    to right,
    #000 var(--progress),
    #bbb var(--progress)
  );
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: --progress 0.1s ease; /* 可选:添加平滑过渡 */
}

JavaScript

document.addEventListener("DOMContentLoaded", () => {
  const quote = document.querySelector(".quote-text");
  if (!quote) return;

  // ----------------------------
  // STEP 1: Split text into lines(这部分逻辑没问题,保留)
  // ----------------------------
  const text = quote.innerText.trim();
  quote.innerHTML = "";
  const words = text.split(" ");
  let lineSpan = document.createElement("span");
  lineSpan.className = "line";
  quote.appendChild(lineSpan);
  let lineText = "";

  words.forEach((word) => {
    const testLine = lineText + word + " ";
    lineSpan.textContent = testLine;
    if (lineSpan.scrollWidth > quote.clientWidth) {
      lineText = word + " ";
      lineSpan = document.createElement("span");
      lineSpan.className = "line";
      lineSpan.textContent = lineText;
      quote.appendChild(lineSpan);
    } else {
      lineText = testLine;
    }
  });

  const lines = quote.querySelectorAll(".line");
  const totalLines = lines.length;
  if (totalLines === 0) return;

  // ----------------------------
  // STEP 2: 调整滚动动画逻辑(关键修改)
  // ----------------------------
  function updateProgress() {
    const rect = quote.getBoundingClientRect();
    const windowHeight = window.innerHeight;
    
    // 计算元素的可见进度:0 = 元素刚进入视口顶部,1 = 元素完全离开视口底部
    let elementProgress = (windowHeight - rect.top) / (windowHeight + rect.height);
    elementProgress = Math.min(Math.max(elementProgress, 0), 1);

    // 每个行对应的动画阶段长度
    const stageLength = 1 / totalLines;

    lines.forEach((line, index) => {
      // 当前行的阶段起始和结束进度
      const stageStart = index * stageLength;
      const stageEnd = (index + 1) * stageLength;

      if (elementProgress < stageStart) {
        // 还没轮到当前行,保持灰色
        line.style.setProperty("--progress", "0%");
      } else if (elementProgress >= stageEnd) {
        // 当前行已经完成动画,全黑
        line.style.setProperty("--progress", "100%");
      } else {
        // 当前行正在动画:计算该行内部的填充进度
        const lineProgress = (elementProgress - stageStart) / stageLength;
        line.style.setProperty("--progress", `${lineProgress * 100}%`);
      }
    });
  }

  window.addEventListener("scroll", updateProgress);
  window.addEventListener("resize", updateProgress);
  updateProgress();
});

关键修改说明

  1. 阶段划分:把整个滚动触发区间(元素从进入到离开视口)分成和行数相等的阶段,每个阶段只对应一行的动画。
  2. 进度判断
    • 如果滚动进度还没到当前行的阶段起始点,该行保持0%进度(全灰)
    • 如果滚动进度超过当前行的阶段结束点,该行设为100%进度(全黑)
    • 处于阶段内时,计算该行内部的相对进度,实现从左到右的填充效果
  3. 平滑过渡:CSS里添加了transition属性,让颜色填充的动画更流畅(可选,你可以根据需要调整时长)

现在滚动页面时,你会看到第一行先从左到右完全变黑,之后第二行才开始动画,以此类推,完全符合你想要的逐行顺序效果。

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

火山引擎 最新活动