如何在Next.js中实现类Nuxt.js/Vue.js网站的流畅动画筛选栏?
在Next.js中实现带流畅动画的动态筛选栏组件
可用工具库推荐
- Framer Motion:React生态适配Next.js的主流动画库,支持状态驱动动画、布局过渡、内容进入/退出动画,能快速实现筛选栏切换时的流畅过渡效果,API简洁易上手。
- Radix UI:无样式的基础交互组件库,提供下拉、切换等原生级交互的底层实现,适合自定义筛选栏的交互逻辑,避免第三方UI库的样式束缚。
- Zustand/React Context:小场景下用React Context管理筛选状态足够,复杂多组件共享状态时,Zustand更轻量高效,可减少不必要的重渲染。
过渡动画实现方案
1. 内容切换的进入/退出动画
用Framer Motion的AnimatePresence组件包裹筛选内容区域,配合motion.div的initial/animate/exit属性,实现切换筛选选项时旧内容淡出、新内容淡入+位移的过渡效果,设置mode="wait"可确保旧动画完成后再启动新动画,避免视觉混乱。
2. 布局自适应动画
给筛选选项按钮添加Framer Motion的layout属性,当选项内容宽度变化或选中状态切换时,元素会自动平滑调整位置和尺寸,无需手动计算过渡参数。
3. 纯CSS过渡(轻量方案)
若不想引入动画库,可通过CSS的transition属性给筛选项的opacity、width、transform等属性添加过渡效果,结合React状态切换控制元素类名,实现基础的平滑过渡。
组件结构建议
拆分粒度
- FilterBar(主容器):负责管理筛选状态、协调子组件交互、包裹动画逻辑。
- FilterOption(选项组件):单个筛选按钮,处理点击事件、自身样式和hover/tap微动画,用
React.memo包裹避免不必要重渲染。 - FilterContent(内容区域):根据选中选项渲染对应内容,包裹在动画容器中,确保内容切换时的过渡效果。
状态管理
- 简单场景:在
FilterBar中用useState管理当前选中的筛选键即可。 - 复杂场景:用Zustand或Context将筛选状态提升,方便其他组件(如列表组件)访问筛选结果。
响应式适配
用Tailwind CSS或原生CSS媒体查询,在小屏幕下将横向筛选栏转为下拉菜单,配合动画组件实现菜单展开/收起的平滑过渡,确保移动端体验一致。
性能优化
- 用
React.memo缓存FilterOption和FilterContent组件,仅在props变化时重渲染。 - 若筛选内容包含大量数据,可结合
React.lazy和Suspense实现懒加载,避免初始加载过慢。
示例代码(基于Framer Motion)
import { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; const FilterBar = () => { const [activeFilter, setActiveFilter] = useState('all'); const filters = [ { key: 'all', label: '全部', content: '展示全部内容' }, { key: 'products', label: '产品', content: '产品分类列表' }, { key: 'services', label: '服务', content: '企业服务内容' }, ]; return ( <div className="flex flex-col gap-4 p-4 border-b"> {/* 筛选选项栏 */} <div className="flex gap-2"> {filters.map((filter) => ( <motion.button key={filter.key} className={`px-4 py-2 rounded-md ${ activeFilter === filter.key ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-800' }`} onClick={() => setActiveFilter(filter.key)} whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.97 }} layout transition={{ duration: 0.2 }} > {filter.label} </motion.button> ))} </div> {/* 筛选内容区域 */} <AnimatePresence mode="wait"> <motion.div key={activeFilter} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.3 }} className="p-3 bg-gray-50 rounded-md" > {filters.find(f => f.key === activeFilter)?.content} </motion.div> </AnimatePresence> </div> ); }; export default FilterBar;
内容的提问来源于stack exchange,提问作者Mansukh Khandhar
相关产品推荐
相关产品推荐

