Framer Motion页面滑动动画中Body背景异常显示问题
页面切换动画间隙露出背景的解决方案
问题根源
- 退出动画时长(1s)与进入/居中动画时长(0.5s)不一致,导致旧页面提前离开视口、新页面未完全到位,出现间隙
- 最外层容器未设置背景色,间隙处直接显示body背景
- 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
相关产品推荐
相关产品推荐

