
本文详解如何在使用 framer motion 实现逐字动画(letter-by-letter)时,正确保留原始文本中的空格与排版结构,避免“web developer”被渲染为“webdeveloper”的常见问题。
本文详解如何在使用 framer motion 实现逐字动画(letter-by-letter)时,正确保留原始文本中的空格与排版结构,避免“web developer”被渲染为“webdeveloper”的常见问题。
在 React + Framer Motion 的逐字动画实践中,一个典型陷阱是:直接对字符串调用 text.split(”) 后映射渲染,虽能拆出每个字符(包括空格),但因 HTML 默认合并连续空白符(如空格、换行),且
✅ 正确方案:保留空格的两种推荐方式
方案一:使用 whiteSpace: ‘pre’(推荐)
为每个
标签一样忠实保留所有空白字符(包括空格、制表符、换行)。同时需移除 jsX 中 {char} 周围的多余空白(即确保模板中无换行/缩进干扰):</p><pre class="brush:php;toolbar:false;">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.span key={index} initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.04 * index }} style={{ whiteSpace: 'pre' }} // ? 关键:保留空格语义 > {char} </motion.span> ))} </div> ); } export default AnimatedText;
? 为什么用
而非 ? 是块级元素,强制换行会破坏内联文本流;而 是内联元素,更符合文字排版逻辑。配合 whiteSpace: ‘pre’,既能保留空格,又保持水平排列。方案二:将空格替换为 (兼容性更强)
若需兼容某些特殊样式环境(如部分 CSS-in-JS 库对 whiteSpace 支持不稳定),可预处理字符串,将普通空格 ‘ ‘ 替换为 HTML 不间断空格实体 ‘u00A0’(即 ):
const characters = text.split('').map(char => char === ' ' ? 'u00A0' : char );再配合默认渲染即可(无需额外 CSS):
{characters.map((char, index) => ( <motion.span key={index} initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.04 * index }} > {char} </motion.span> ))}⚠️ 注意事项
- 避免在 map 渲染中添加换行或缩进(如
n {char}n ),这会引入额外空白节点,干扰布局;- 若文本含多个连续空格(如 “a b”),whiteSpace: ‘pre’ 会完整保留;而 替换仅处理单个空格,如需支持多空格,建议用正则 / /g 全局替换;
- 动画容器(如外层
)应设置 gap 或 margin 控制字间距,而非依赖空格——空格仅用于语义分隔,视觉间距建议由 CSS 精确控制。✅ 总结
保留单词间空格的本质,是让浏览器正确解析并渲染空格字符。whiteSpace: ‘pre’ 是最语义清晰、维护成本最低的解法; 替换则提供更广泛的兼容性兜底。二者均可无缝集成到现有 Framer Motion 动画逻辑中,无需修改动画配置,即可实现专业级的逐字文本动效。