如何用Framer Motion实现React组件间带滑动手势的转场效果?
React + Framer Motion 实现带滑动转场的页面切换
核心方案选择
用 Drag 而非 Swipe:Swipe 是触发一次性动作的快捷方式,而 Drag 支持交互式拖拽(按住时实时切换页面状态,松开后确认结果),完全匹配你的需求。
完整实现代码
以下是调整后的代码,同时支持按钮切换和滑动切换,且仅在首次拖拽时加载目标页面:
// Test.jsx import { motion, AnimatePresence } from 'framer-motion'; import { useState, useRef } from 'react'; import { Box } from './Box'; export function Test() { const [currentPage, setCurrentPage] = useState(1); const [isDragging, setIsDragging] = useState(false); const currentPageRef = useRef(null); // 拖拽结束判断是否切换页面 const handleDragEnd = (_, info) => { const threshold = window.innerWidth * 0.3; // 切换阈值设为屏幕宽度30% if (info.offset.x < -threshold) { setCurrentPage(prev => prev + 1); } else if (info.offset.x > threshold) { setCurrentPage(prev => prev - 1); } setIsDragging(false); }; // 开始拖拽时标记状态,加载目标页面 const handleDragStart = () => { setIsDragging(true); }; // 按钮切换逻辑 const handleButtonToggle = () => { setCurrentPage(prev => prev + 1); }; // 生成页面动画配置 const getPageMotionProps = (pageNum) => { const isNextPage = pageNum > currentPage; return { style: { position: 'absolute', width: '100%', height: '116px' }, initial: isNextPage ? { x: '100%', opacity: 0 } : { x: '-100%', opacity: 0 }, animate: { x: 0, opacity: 1 }, exit: isNextPage ? { x: '-100%', opacity: 0 } : { x: '100%', opacity: 0 }, transition: { duration: 0.5 } }; }; return ( <> {/* 页面容器:相对定位,隐藏溢出内容 */} <div style={{ position: 'relative', width: '100%', height: '116px', overflow: 'hidden' }}> <AnimatePresence initial={false} mode="popLayout"> {/* 当前页面:支持横向拖拽 */} <motion.div key={currentPage} {...getPageMotionProps(currentPage)} drag="x" dragConstraints={{ left: 0, right: 0 }} dragElastic={0.1} onDragStart={handleDragStart} onDragEnd={handleDragEnd} ref={currentPageRef} > <Box n={currentPage} /> </motion.div> {/* 拖拽时才渲染目标页面,实现懒加载 */} {isDragging && ( <motion.div key={currentPage + 1} {...getPageMotionProps(currentPage + 1)} style={{ ...getPageMotionProps(currentPage + 1).style, x: currentPageRef.current ? currentPageRef.current.getBoundingClientRect().width + currentPageRef.current._x : 0 }} > <Box n={currentPage + 1} /> </motion.div> )} </AnimatePresence> </div> <br /><br /> <button type="button" onClick={handleButtonToggle}> 切换页面 </button> </> ); }
关键细节说明
- 拖拽交互:
drag="x"开启横向拖拽,dragConstraints限制拖拽边界,dragElastic调整拖拽时的弹性效果,让交互更自然。 - 懒加载目标页面:通过
isDragging状态控制目标页面的渲染,只有用户开始拖拽时才加载,避免预加载隐藏页面。 - 切换阈值:拖拽距离超过屏幕30%时触发页面切换,可根据需求调整阈值比例。
- 动画同步:当前页面和目标页面的入场/退场动画方向相反,保证转场过程流畅连贯。
扩展到路由场景
如果要将页面切换和路由绑定(比如使用 react-router),只需将页面状态替换为路由参数:
import { useParams, useNavigate } from 'react-router-dom'; export function Test() { const { pageId } = useParams(); const navigate = useNavigate(); const currentPage = parseInt(pageId) || 1; const [isDragging, setIsDragging] = useState(false); const currentPageRef = useRef(null); const handleDragEnd = (_, info) => { const threshold = window.innerWidth * 0.3; if (info.offset.x < -threshold) { navigate(`/page/${currentPage + 1}`); } else if (info.offset.x > threshold) { navigate(`/page/${currentPage - 1}`); } setIsDragging(false); }; // 其他逻辑保持不变... }
内容的提问来源于stack exchange,提问作者ephramd
相关产品推荐
相关产品推荐

