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

React中SplitText组件渐变异常:仅作用于单个字符而非整词

React SplitText组件渐变效果仅作用于单个字符的修复方案

问题说明

在React项目中使用SplitText组件时,为拆分文本设置的渐变效果仅作用于单个字符,而非整个单词。调整过组件代码、CSS及JS配置,均未解决该问题。

相关代码

SplitText组件

import { useSprings, animated } from '@react-spring/web';
import { useEffect, useRef, useState } from 'react';

const SplitText = ({
  text = '',
  className = '',
  delay = 100,
  animationFrom = { opacity: 0, transform: 'translate3d(0,40px,0)' },
  animationTo = { opacity: 1, transform: 'translate3d(0,0,0)' },
  easing = 'easeOutCubic',
  threshold = 0.1,
  rootMargin = '-100px',
  onLetterAnimationComplete,
  
}) => {
  const words = text.split(' ').map((word) => word.split(''));
  const letters = words.flat();
  const [inView, setInView] = useState(false);
  const ref = useRef();
  const animatedCount = useRef(0);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setInView(true);
          observer.unobserve(ref.current);
        }
      },
      { threshold, rootMargin }
    );

    observer.observe(ref.current);

    return () => observer.disconnect();
  }, [threshold, rootMargin]);

  const springs = useSprings(
    letters.length,
    letters.map((_, i) => ({
      from: animationFrom,
      to: inView
        ? async (next) => {
            await next(animationTo);
            animatedCount.current += 1;
            if (animatedCount.current === letters.length && onLetterAnimationComplete) {
              onLetterAnimationComplete();
            }
          }
        : animationFrom,
      delay: i * delay,
      config: { easing },
    }))
  );

  return (
    <div
      ref={ref}
      className={`split-parent ${className}`}
    >
      {words.map((word, wordIndex) => (
        <span
          key={wordIndex}
          style={{
            background: "linear-gradient(90deg, #27093B 0%, #B242FF 40.5%, #B242FF 59%, #27093B 100%)",
            WebkitBackgroundClip: "text",
            color: "transparent",
            display: 'inline-block',
          }}
        >
          {word.map((letter, letterIndex) => {
            const index = words
              .slice(0, wordIndex)
              .reduce((acc, w) => acc + w.length, 0) + letterIndex;

            return (
              <animated.span
                key={index}
                style={{
                  ...springs[index],
                  display: 'inline-block',
                  willChange: 'transform, opacity',
                  color: 'inherit',
                }}
              >
                {letter}
              </animated.span>
            );
          })}
          <span style={{ display: 'inline-block', width: '0.3em' }}>&nbsp;</span>
        </span>
      ))}
    </div>
  );
};

export default SplitText;

原有CSS

.hero .marketing {
    font-family: 'Urbanist', sans-serif;
    font-weight: bolder;
    font-size: 198px;
    font-weight: 900;
    left: 0;
    letter-spacing: 0;
    line-height: normal;
    text-align: center;
    top: 0;
    padding-top: 127px;
    z-index: 1;
    background: linear-gradient(90deg, #27093B 0%, #B242FF 40.5%, #B242FF 59%, #27093B 100%);
    background-clip: text;
  }
   
  .hero .digital {
    font-family: 'Urbanist', sans-serif;
    font-weight: bolder;
    font-size: 198px;
    font-weight: 900;
    left: 0;
    letter-spacing: 0;
    line-height: normal;
    text-align: center;
    top: 0;
    margin-top: -85px;
  }

组件调用代码

<SplitText
    text="DIGITAL"
    className="digital"
    delay={150}
    animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }}
    animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }}
    easing="easeOutCubic"
    threshold={0.2}
    rootMargin="-50px"
/>
<SplitText
    text="MARKETING"
    className="marketing"
    delay={150}
    animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }}
    animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }}
    easing="easeOutCubic"
    threshold={0.2}
    rootMargin="-50px"
/>

解决方案说明

问题根源:原组件将渐变设置在最外层父容器,而每个字母被包装为独立的inline-block元素,background-clip: text会对每个独立的文本元素单独生效,导致每个字符都显示完整的渐变,而非整个单词连贯渐变。

修复步骤:

  1. 将渐变样式从最外层父容器移到每个单词对应的span标签上,让单词成为渐变的载体
  2. 给字母的animated.span设置color: inherit,继承单词span的透明文本属性
  3. 移除最外层父容器的渐变样式,避免样式冲突
  4. 若CSS类中已设置渐变,可选择移除组件内的渐变样式,统一通过CSS类控制(保持样式一致性)

修改后的CSS可简化为(移除重复的渐变设置):

.hero .marketing {
    font-family: 'Urbanist', sans-serif;
    font-weight: 900;
    font-size: 198px;
    left: 0;
    letter-spacing: 0;
    line-height: normal;
    text-align: center;
    top: 0;
    padding-top: 127px;
    z-index: 1;
}
   
.hero .digital {
    font-family: 'Urbanist', sans-serif;
    font-weight: 900;
    font-size: 198px;
    left: 0;
    letter-spacing: 0;
    line-height: normal;
    text-align: center;
    top: 0;
    margin-top: -85px;
}

这样调整后,渐变会作用于整个单词,每个字母仅负责动画效果,继承单词的渐变文本样式,最终实现整个单词的连贯渐变效果。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 16:10:54