
本文介绍如何在使用 framer motion 对文本进行逐字(letter-by-letter)动画时,准确保留单词间的空格,避免因字符串拆分导致的空格丢失问题。
本文介绍如何在使用 framer motion 对文本进行逐字(letter-by-letter)动画时,准确保留单词间的空格,避免因字符串拆分导致的空格丢失问题。
在构建精细的文字动效时,开发者常通过 text.split('') 将字符串转为字符数组,再用 motion.div 逐项渲染并添加动画。但该方法存在一个隐蔽陷阱:HTML 默认会合并连续空白符(包括空格、换行、制表符),且
✅ 正确方案:保留空格的两种可靠方式
方案一:使用 CSS white-space: pre(推荐)
为每个 motion.div 设置 white-space: pre,强制浏览器按源码保留空格与换行。但需注意:JSX 中 {char} 前后默认存在换行和缩进,会额外引入空白节点。因此必须移除 JSX 换行与空格,并确保 motion.div 内仅包裹纯字符:
import { motion } from 'framer-motion';
type Props = {
text: string;
};
function AnimatedText({ text }: Props) {
const characters = text.split('');
return (
<div className="flex">
{characters.map((char, index) => (
<motion.div
key={index}
className="inline-block"
style={{ whiteSpace: 'pre' }} // ? 关键:保留空格
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.04 * index }}
>
{char}
</motion.div>
))}
</div>
);
}
export default AnimatedText;? 提示:添加 className="inline-block" 可避免 div 默认块级行为干扰行内布局;whiteSpace: 'pre' 确保空格字符(U+0020)被渲染为可见空白。
方案二:将空格替换为 (非破坏性兼容方案)
若无法修改样式或需兼容更旧环境,可在拆分后预处理字符:将普通空格 ' ' 替换为 HTML 不间断空格实体('\u00A0'),它始终占据宽度且不会被折叠:
const characters = text.split('').map(char =>
char === ' ' ? '\u00A0' : char
);然后直接渲染,无需额外 CSS:
{characters.map((char, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.04 * index }}
>
{char}
</motion.div>
))}⚠️ 注意事项与最佳实践
- 避免使用 字符串字面量:直接写 在 JSX 中无效,必须用 Unicode 转义 \u00A0 或 String.fromCharCode(160);
- 性能考量:对超长文本(如 >500 字符),逐字符渲染可能触发大量 DOM 节点,建议结合 React.memo 或节流策略;
-
语义化补充:若动画文本具有标题语义,可在外层包裹
并设置 aria-label={text},确保无障碍访问;
- 响应式适配:空格宽度受字体、字号影响,建议搭配 font-variant-numeric: tabular-nums 等属性保持等宽一致性(尤其数字场景)。
通过上述任一方式,你均可精准还原 'Web Developer' 的原始间距,并赋予每个字符独立、可控的进入动画——既保持设计意图,又不失工程健壮性。










