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

Framer Motion布局平滑过渡问题:文本缩短时容器尺寸跳变修复求助

修复Framer Motion轮播文字容器尺寸平滑过渡问题(文本缩短时跳变)

问题描述

使用Framer Motion实现轮播文字效果时,容器在文本变长时能平滑放大,但文本缩短时会突然跳变到新尺寸,无法平滑过渡。

问题原因

当前实现中,AnimatePresence会在旧元素退出动画结束后将其从DOM中移除:

  • 文本变长时,新元素宽度更大,进入时会立即撑开容器,结合layout prop实现平滑放大;
  • 文本缩短时,旧元素退出后DOM中只剩更窄的新元素,容器失去旧元素的宽度支撑,瞬间缩小导致跳变。

解决方案

方法一:手动控制新旧元素共存至过渡完成

通过维护previous状态,让旧元素在容器完成宽度过渡后再移除,确保过渡期间容器始终有足够的宽度支撑,实现平滑收缩。

修改后的代码:

import React from 'react';
import { motion } from 'framer-motion';

interface RotatingTextProps {
  words: string[];
}

function RotatingText(props: RotatingTextProps) {
  const { words } = props;

  const [current, setCurrent] = React.useState(0);
  const [previous, setPrevious] = React.useState<number | null>(null);

  React.useEffect(() => {
    const interval = setInterval(() => {
      // 先记录当前索引为旧元素
      setPrevious(current);
      // 更新当前索引为下一个文字
      setCurrent((curr) => (curr + 1) % words.length);
      // 等待过渡完成后移除旧元素(与过渡时间保持一致)
      setTimeout(() => setPrevious(null), 500);
    }, 2000);

    return () => clearInterval(interval);
  }, [current, words.length]);

  return (
    <motion.div
      layout
      transition={{ duration: 0.5 }}
      style={{
        backgroundColor: 'yellow',
        display: 'inline-block',
      }}
    >
      <h1
        style={{
          display: 'inline-grid',
          overflow: 'hidden',
        }}
      >
        {/* 渲染旧元素并执行退出动画 */}
        {previous !== null && (
          <motion.span
            key={previous}
            animate={{ transform: 'translateY(-100%)', opacity: 0 }}
            transition={{ duration: 0.5, ease: 'easeInOut' }}
            style={{ display: 'inline-block', gridColumn: 1, gridRow: 1 }}
          >
            {words[previous]}
          </motion.span>
        )}
        {/* 渲染新元素并执行进入动画 */}
        <motion.span
          key={current}
          initial={{ transform: 'translateY(100%)', opacity: 0 }}
          animate={{ transform: 'translateY(0%)', opacity: 1 }}
          transition={{ duration: 0.5, ease: 'easeInOut' }}
          style={{ display: 'inline-block', gridColumn: 1, gridRow: 1 }}
        >
          {words[current]}
        </motion.span>
      </h1>
    </motion.div>
  );
}

方法二:使用LayoutGroup共享布局变化

利用Framer Motion的LayoutGroup组件,让容器和轮播文字共享布局状态,自动捕捉尺寸变化并实现平滑过渡。

修改后的代码:

import React from 'react';
import { motion, AnimatePresence, LayoutGroup } from 'framer-motion';

interface RotatingTextProps {
  words: string[];
}

function RotatingText(props: RotatingTextProps) {
  const { words } = props;

  const [current, setCurrent] = React.useState(0);

  React.useEffect(() => {
    const interval = setInterval(
      () => setCurrent((curr) => (curr + 1) % words.length),
      2000
    );
    return () => clearInterval(interval);
  }, [words.length]);

  return (
    <LayoutGroup>
      <motion.div
        transition={{ duration: 0.5 }}
        style={{
          backgroundColor: 'yellow',
          display: 'inline-block',
        }}
      >
        <h1
          style={{
            display: 'inline-grid',
            overflow: 'hidden',
          }}
        >
          <AnimatePresence initial={false} mode="sync">
            <motion.span
              key={current}
              layout // 添加layout prop,让LayoutGroup捕捉尺寸变化
              initial={{ transform: 'translateY(100%)', opacity: 0 }}
              animate={{ transform: 'translateY(0%)', opacity: 1 }}
              exit={{ transform: 'translateY(-100%)', opacity: 0 }}
              transition={{ duration: 0.5, ease: 'easeInOut' }}
              style={{ display: 'inline-block', gridColumn: 1, gridRow: 1 }}
            >
              {words[current]}
            </motion.span>
          </AnimatePresence>
        </h1>
      </motion.div>
    </LayoutGroup>
  );
}

说明:mode="sync"让新旧元素的动画同时执行,配合layout prop和LayoutGroup,容器会基于新旧元素的尺寸变化自动平滑过渡宽度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:42:06