如何在 Framer Motion 中实现逐字动画并保留单词间距

4次阅读

如何在 Framer Motion 中实现逐字动画并保留单词间距

本文详解如何使用 framer motion 对文本进行逐字动画时,正确保留原始空格(包括单词间空格),避免字符粘连,并提供可直接复用的解决方案与最佳实践。

本文详解如何使用 framer motion 对文本进行逐字动画时,正确保留原始空格(包括单词间空格),避免字符粘连,并提供可直接复用的解决方案与最佳实践。

在使用 framer-motion 实现逐字动画时,一个常见陷阱是:对字符串调用 .split(”) 后直接映射渲染,会导致 HTML 默认的空白符折叠行为生效——即多个连续空格、换行或制表符被压缩为单个空格,而更关键的是,相邻 元素间的换行与缩进会被浏览器解析为普通空格,最终破坏原始排版结构。例如 ‘Web Developer’ 经 split(”) 后生成 [‘W’,’e’,’b’,’ ‘,’D’,’e’,’v’,’e’,’l’,’o’,’p’,’e’,’r’],但若每个字符包裹在独立块级元素中且未控制空白处理方式,渲染结果可能意外丢失空格或出现多余间隙。

✅ 正确做法是 显式保留空格语义,推荐以下两种稳健方案:

方案一:使用 whiteSpace: ‘pre’ + 清除模板空白(推荐)

为每个 motion.div 设置 CSS whiteSpace: ‘pre’,使其像

标签一样保留所有空白符(含空格、换行、制表符)。同时需移除 JSX 中因格式化产生的额外空格,避免干扰:</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.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;

⚠️ 注意:务必移除 {char} 周围的换行与缩进(如写成 {char} 紧贴标签),否则 JSX 解析会插入额外空格节点。上述代码中使用 className=”inline-block” 是为了确保视觉流式排列,避免 div 默认块级行为导致换行。

方案二:将空格替换为  (非空格字符)

若需更高兼容性或规避 CSS 白空间策略,可预处理字符串,将普通空格 ‘ ‘ 替换为 HTML 不间断空格实体 ‘u00A0’(即  ):

const characters = text   .split('')   .map(char => char === ' ' ? 'u00A0' : char);

再配合默认样式即可,无需额外 CSS。但注意:此法仅解决空格,不处理制表符或换行符;且   在语义上不可被屏幕阅读器自然识别为分隔符,对无障碍支持稍弱。

总结与建议

  • 首选方案一:whiteSpace: ‘pre’ 简洁、语义清晰、支持全类型空白符;
  • 避免依赖父容器 whiteSpace: ‘pre-wrap’ —— 它会影响整个文本流,而非单个字符;
  • 如需支持多行文本动画,建议结合 display: ‘inline-block’ 与 verticalAlign: ‘top’ 防止基线错位;
  • 动画性能敏感场景下,可考虑用 替代 减少 dom 节点开销(motion.span 同样有效)。

通过精准控制空白符渲染行为,你既能享受 Framer Motion 强大的逐帧控制力,又能完全忠于原文本的排版意图。

text=ZqhQzanResources