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

Framer Motion页面滑动动画中Body背景异常显示问题

页面切换动画间隙露出背景的解决方案

问题根源

  1. 退出动画时长(1s)与进入/居中动画时长(0.5s)不一致,导致旧页面提前离开视口、新页面未完全到位,出现间隙
  2. 最外层容器未设置背景色,间隙处直接显示body背景
  3. zIndex层级逻辑不严谨,动画过程中页面覆盖关系错乱

修复方案

1. 统一动画时长

将exit动画的duration改为和enter、center一致的0.5s,保证新旧页面动画同步:

const variants = {
  enter: (direction: number) => ({
    x: direction > 0 ? xMax : xMin,
    transition: { type: "tween", duration: 0.5, ease: "linear" },
  }),
  center: {
    zIndex: 2, // 提高当前页面层级
    x: 0,
    transition: { type: "tween", duration: 0.5, ease: "linear" },
  },
  exit: (direction: number) => ({
    zIndex: 1, // 退出页面层级低于新页面
    x: direction < 0 ? xMax : xMin,
    transition: { type: "tween", duration: 0.5, ease: "linear" }, // 统一时长
  }),
};

2. 给父容器添加背景色

在最外层容器设置背景色,即使有间隙也不会露出body:

<div
  style={{
    width: "100vw",
    height: "100vh",
    position: "relative",
    overflowX: "hidden",
    backgroundColor: "#000", // 可根据需求调整颜色
  }}
>
  {/* ... */}
</div>

3. 完整修复代码

import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";

const xMax = "100vw";
const xMin = "-100vw";

const variants = {
  enter: (direction: number) => ({
    x: direction > 0 ? xMax : xMin,
    transition: { type: "tween", duration: 0.5, ease: "linear" },
  }),
  center: {
    zIndex: 2,
    x: 0,
    transition: { type: "tween", duration: 0.5, ease: "linear" },
  },
  exit: (direction: number) => ({
    zIndex: 1,
    x: direction < 0 ? xMax : xMin,
    transition: { type: "tween", duration: 0.5, ease: "linear" },
  }),
};

export default function App() {
  const [[page, direction], setPage] = useState([0, 0]);

  const paginate = (newDirection: number) => {
    setPage([page + newDirection, newDirection]);
  };

  return (
    <div
      style={{
        width: "100vw",
        height: "100vh",
        position: "relative",
        overflowX: "hidden",
        backgroundColor: "#000",
      }}
    >
      <AnimatePresence initial={false} custom={direction}>
        <motion.div
          key={page}
          custom={direction}
          variants={variants}
          initial="enter"
          animate="center"
          exit="exit"
          style={{
            position: "absolute",
            width: "100%",
            height: "100%",
          }}
        >
          <GetDivs page={page} paginate={paginate} />
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

function GetDivs({
  page,
  paginate,
}: { page: number; paginate: (newDirection: number) => void }) {
  switch (page) {
    case 0:
      return <FirstDiv paginate={paginate} />;
    case 1:
      return <SecondDiv paginate={paginate} />;
  }
  return <ThirdDiv paginate={paginate} />;
}

function FirstDiv({ paginate }: { paginate: (newDirection: number) => void }) {
  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "red",
      }}
    >
      <button
        onClick={() => paginate(1)}
        style={{ position: "absolute", right: 20, top: 20 }}
      >
        Next
      </button>
      <p style={{ fontSize: 40, margin: 0 }}>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, atque
        possimus, est amet eos, dolore impedit incidunt labore veritatis ratione
        eum perferendis exercitationem ex recusandae corrupti illum molestias.
        Minus, corporis! Itaque, sit quaerat? Cumque qui minus voluptatum
        officia nam debitis ipsam corrupti illum voluptatem ipsa, omnis at vitae
        ut cupiditate, placeat vero deserunt quis. Iste eaque itaque cumque
        mollitia voluptate minima sit optio voluptatem similique, facere sunt
        architecto corrupti suscipit officiis tenetur, nihil ipsum. Veniam quae,
        optio maxime atque debitis enim repellendus perspiciatis ipsam nisi iste
        laboriosam excepturi ratione id itaque nihil omnis aperiam. Voluptate
        natus reprehenderit tempore amet, ipsa nemo alias odit dolorum
        voluptatem! Vitae quam deserunt corrupti veritatis consequatur nulla ea
        suscipit quas repellendus minima, dolorem beatae sint iste dolor dicta
        tempore cum quidem assumenda itaque reiciendis fuga voluptatibus! Aut
        tenetur sed distinctio error culpa, quis eligendi possimus deleniti
        quasi. Cumque molestias sit porro saepe vero aliquid. Aspernatur?
      </p>
    </div>
  );
}

function SecondDiv({ paginate }: { paginate: (newDirection: number) => void }) {
  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "green",
      }}
    >
      <button
        onClick={() => paginate(-1)}
        style={{ position: "absolute", left: 20, top: 20 }}
      >
        Back
      </button>
      <button
        onClick={() => paginate(1)}
        style={{ position: "absolute", right: 20, top: 20 }}
      >
        Next
      </button>
    </div>
  );
}

function ThirdDiv({ paginate }: { paginate: (newDirection: number) => void }) {
  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        backgroundColor: "blue",
      }}
    >
      <button
        onClick={() => paginate(-1)}
        style={{ position: "absolute", left: 20, top: 20 }}
      >
        Back
      </button>
    </div>
  );
}

效果说明

  • 同步动画时长后,新旧页面切换无时间差,不会出现间隙
  • 父容器背景色兜底,避免极端情况露出body
  • 层级调整确保新页面始终覆盖旧页面,彻底解决背景暴露问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:59:52