
本文介绍如何在使用 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’ 的原始间距,并赋予每个字符独立、可控的进入动画——既保持设计意图,又不失工程健壮性。