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

带渐变文字的无限滚动跑马灯Safari卡顿重叠问题排查及替代方案咨询

带渐变文字的无限滚动跑马灯Safari卡顿重叠问题排查及替代方案咨询

我完全懂你现在的困扰——这个带渐变文字的无限跑马灯在其他浏览器都好好的,到Safari就卡得不行还出现文字重叠,太影响体验了。咱们先看看你的现有实现,再一步步聊优化思路和替代方案。

你的当前实现代码

先把你提供的代码整理好,方便对照:

HTML

<div class="running-lines">
  <div class="running-lines__wrapper" data-duration="28000" data-duplicate-count="7">
    <p class="running-lines__items">
      <span class="running-lines__item">Security Kit</span>
      <span class="running-lines__item">Honeypot</span>
      <span class="running-lines__item">Antibot</span>
      <span class="running-lines__item">Flood Control</span>
      <span class="running-lines__item">Move 403 to 404</span>
      <span class="running-lines__item">Rabbit Hole</span>
      <span class="running-lines__item">Username Enumeration Prevention</span>
    </p>
  </div>
  <div class="running-lines__wrapper" data-duration="50000" data-direction="reverse" data-duplicate-count="7">
    <p class="running-lines__items">
      <span class="running-lines__item">Content Moderation</span>
      <span class="running-lines__item">Workflows</span>
      <span class="running-lines__item">Simple Menu Permissions</span>
      <span class="running-lines__item">Group Node</span>
      <span class="running-lines__item">Scheduler</span>
      <span class="running-lines__item">Media</span>
    </p>
  </div>
  <div class="running-lines__wrapper" data-duration="40000" data-duplicate-count="7">
    <p class="running-lines__items">
      <span class="running-lines__item">Paragraphs & Paragraphs Browser</span>
      <span class="running-lines__item">Group</span>
      <span class="running-lines__item">Gin & Gin Toolbar</span>
      <span class="running-lines__item">Crop & Image Widget Crop</span>
      <span class="running-lines__item">Migrate & Migrate Plus</span>
    </p>
  </div>
</div>

CSS

.running-lines {
  font-family: "Poppins";
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  row-gap: 0.5rem;
  overflow: hidden;
  background-image: linear-gradient(92.55deg,
      #852fff 0%,
      #dd26ed 48.5%,
      #ed2662 100%);
  -webkit-background-clip: text;
  background-clip: text;
  padding-top: 4rem;
  padding-bottom: 4rem;
  mix-blend-mode: lighten;
}

@media (min-width: 956px) {
  .running-lines {
    row-gap: 1rem;
    padding-top: 3.5rem;
    padding-bottom: 3.5rem;
  }
}

@media (min-width: 1184px) {
  .running-lines {
    row-gap: 1.25rem;
  }
}

.running-lines__wrapper {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
}

.running-lines__items {
  margin-bottom: 0;
  text-wrap: nowrap;
  will-change: text-indent;
  /*   animation: marquee 15s linear infinite; */
}

.running-lines__item {
  margin-right: 2rem;
  font-size: 1.25rem;
  line-height: 1.5rem;
  font-style: italic;
  color: transparent;
}

@media (min-width: 956px) {
  .running-lines__item {
    margin-right: 3rem;
    font-size: 1.5rem;
    line-height: 2rem;
  }
}

@media (min-width: 1184px) {
  .running-lines__item {
    margin-right: 4rem;
  }
}

@keyframes marquee {
  from {
    text-indent: 0;
  }

  to {
    text-indent: -100%;
  }
}

JavaScript

document.querySelectorAll('.running-lines__wrapper').forEach(
  (line) => {
    const content = line.innerHTML;
    const duplicateCount = line.getAttribute('data-duplicate-count') || 1;
    const duration =
      parseInt(line.getAttribute('data-duration'), 10) || 10000;
    const direction = line.getAttribute('data-direction') || 'normal';
    let duplicatedContent = '';

    for (let i = 0; i < duplicateCount; i++) {
      duplicatedContent += content;
    }

    line.innerHTML = duplicatedContent;

    const lineText = line.querySelectorAll('.running-lines__items');

    lineText.forEach((text) => {
      const fromFrame = {
        textIndent: 0
      };
      const toFrame = {
        textIndent: '-100%'
      };
      const options = {
        duration,
        iterations: Infinity,
        direction
      };
      text.animate([fromFrame, toFrame], options);
    });
  });

问题根源分析

Safari出现卡顿和重叠主要有这几个原因:

  • Web Animations API性能问题:你用的element.animate()在Safari里对text-indent这类布局属性的动画支持不够流畅,尤其是结合background-clip: text的渐变文字时,渲染开销会大幅增加,容易掉帧。
  • text-indent计算差异:Safari对text-indent: -100%的解析和其他浏览器有细微差别,加上重复内容过多,容易导致文字重叠。
  • 渲染叠加开销:mix-blend-mode: lighten和渐变文字组合,会让Safari的渲染引擎额外计算图层混合,进一步降低性能。

针对Safari的优化方案

1. 替换Web Animations API为CSS动画

你已经写好了@keyframes marquee,直接用CSS的animation属性代替JS的动画创建,Safari对CSS动画的兼容性和性能要好很多。

修改步骤:

  • 调整JS代码,去掉动画创建部分,改为给元素添加样式:
document.querySelectorAll('.running-lines__wrapper').forEach(
  (line) => {
    const content = line.innerHTML;
    const duplicateCount = line.getAttribute('data-duplicate-count') || 1;
    const duration =
      parseInt(line.getAttribute('data-duration'), 10) || 10000;
    const direction = line.getAttribute('data-direction') || 'normal';
    let duplicatedContent = '';

    for (let i = 0; i < duplicateCount; i++) {
      duplicatedContent += content;
    }

    line.innerHTML = duplicatedContent;

    const lineText = line.querySelectorAll('.running-lines__items');
    lineText.forEach((text) => {
      // 用CSS动画替代JS动画
      text.style.animation = `marquee ${duration}ms linear infinite ${direction}`;
    });
  });
  • 去掉CSS中.running-lines__items的will-change: text-indent(换成CSS动画后不需要了),或者改成will-change: transform(后续优化会用到)。

2. 优化渐变文字渲染

在.running-lines__item里添加Safari专属的渲染优化属性:

.running-lines__item {
  /* 原有属性不变 */
  -webkit-font-smoothing: antialiased;
  -webkit-text-stroke: 0.5px transparent;
}

如果mix-blend-mode: lighten不是必须的,建议去掉它,能大幅减少渲染压力。

3. 减少重复内容的数量

data-duplicate-count="7"太多了,会导致DOM内容过长。你可以计算刚好覆盖视口2倍宽度的重复次数,比如改成2-3次,既能实现无限滚动,又不会增加不必要的渲染负担。

更稳定的替代方案:用GPU加速的Transform实现

transform: translateX()是GPU加速的属性,在Safari里性能远优于text-indent,而且不会出现文字重叠问题。实现思路是把跑马灯内容复制一份,用flex布局排列,然后通过transform: translateX(-50%)实现无限滚动。

示例代码(修改版)

HTML
<div class="running-lines">
  <div class="running-lines__wrapper" data-duration="28000" data-direction="normal">
    <div class="running-lines__track">
      <div class="running-lines__items">
        <span class="running-lines__item">Security Kit</span>
        <span class="running-lines__item">Honeypot</span>
        <span class="running-lines__item">Antibot</span>
        <span class="running-lines__item">Flood Control</span>
        <span class="running-lines__item">Move 403 to 404</span>
        <span class="running-lines__item">Rabbit Hole</span>
        <span class="running-lines__item">Username Enumeration Prevention</span>
      </div>
      <!-- 复制一份内容,实现无缝滚动 -->
      <div class="running-lines__items">
        <span class="running-lines__item">Security Kit</span>
        <span class="running-lines__item">Honeypot</span>
        <span class="running-lines__item">Antibot</span>
        <span class="running-lines__item">Flood Control</span>
        <span class="running-lines__item">Move 403 to 404</span>
        <span class="running-lines__item">Rabbit Hole</span>
        <span class="running-lines__item">Username Enumeration Prevention</span>
      </div>
    </div>
  </div>
  <!-- 其他行类似,复制内容即可 -->
</div>
CSS
.running-lines {
  /* 原有属性不变,去掉mix-blend-mode: lighten(可选) */
}

.running-lines__wrapper {
  overflow: hidden; /* 隐藏超出部分 */
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
}

.running-lines__track {
  display: flex;
  will-change: transform; /* 告诉浏览器提前准备GPU加速 */
}

.running-lines__items {
  display: flex;
  gap: 2rem; /* 替代margin-right,更灵活 */
  flex-shrink: 0; /* 防止内容被压缩 */
}

.running-lines__item {
  /* 原有属性不变,去掉margin-right */
}

/* 动画改为transform */
@keyframes marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

@keyframes marquee-reverse {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}
JavaScript
document.querySelectorAll('.running-lines__wrapper').forEach(
  (line) => {
    const duration = parseInt(line.getAttribute('data-duration'), 10) || 10000;
    const direction = line.getAttribute('data-direction') || 'normal';
    const track = line.querySelector('.running-lines__track');
    // 根据方向选择动画
    const animationName = direction === 'reverse' ? 'marquee-reverse' : 'marquee';
    track.style.animation = `${animationName} ${duration}ms linear infinite`;
  });

这个方案用GPU加速的transform,在Safari里几乎不会卡顿,而且因为复制了完整的内容,不会出现文字重叠的问题,是目前最稳定的实现方式。

总结

优先尝试把Web Animations API换成CSS动画,结合渐变文字的渲染优化;如果还是有问题,就改用transform的方案,这个在Safari里的兼容性和性能都更可靠。

备注:内容来源于stack exchange,提问作者Clop1986

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 18:58:02