前言
在前端开发过程中,经常会遇到将一块区域收起展开的需求。如果只是简单的显示隐藏会很突兀,用户体验很差。所以需要一个过渡动画使之平滑。framer-motion就是一个不错的选择。链接:www.framer.com/motion/
思路:
使用motion.div做一个动画壳子并定义过渡效果和触发时机。
关联到需要收起展开的内容并使之通用
安装并导入framer-motion,使用<motion.div>作为动画壳子
npm i framer-motion
import { motion } from 'framer-motion'
<motion.div>
<div>需要收起展开的内容</div>
</motion.div>
过渡效果
展开时,开始不显示,结束时完全显示,持续时间约0.3秒,过程中高度不断增加。
收起时,开始完全显示,结束不显示,持续时间约0.3秒,过程中高度不断减少。
1.初始状态:
initial={{ height: 0 }}
2.动画:当height属性变化时执行动画,slotHeight是需要收起展开的内容的高度。
animate={{ height: slotHeight }}
3.过渡:(动画持续0.3秒) transition={{ duration: 0.3 }}
4.退出(收起)
exit={{ height: 0 }}
5.壳子的效果虽然定义好了,但里面的内容始终处于显示的状态。所以增加一条样式。
style={{ overflow: 'hidden' }}
触发
触发收起展开,点击收起展开时,给isShow取反。
const [isShow,setIsShow]=useState(false)
获得收起展开的内容高度并基于isShow赋值
const slotRef = useRef(null)
const [slotHeight, setSlotHeight] = useState(0)
useEffect(() => {
slotRef?.current && setSlotHeight(slotRef?.current?.offsetHeight || 0)
}, [isShow])
<div ref="slotRef">需要收起展开的内容</div>
退出动画需要特殊处理,AnimatePresence可以使组件销毁时,仍有动画效果。
import { AnimatePresence } from 'framer-motion'
<AnimatePresence>
isShow && (<motion.div> <div>需要收起展开的内容</div></motion.div>)}
</AnimatePresence>
通用
把slotRef和slotHeight写进custom hook
import { useEffect, useRef, useState } from 'react'
export const useCollapseExpand = (isShow) => {
const slotRef = useRef(null)
const [slotHeight, setSlotHeight] = useState(0)
useEffect(() => {
slotRef?.current && setSlotHeight(slotRef?.current?.offsetHeight || 0)
}, [isShow])
return { slotRef, slotHeight }
}
把需要收起展开的内容抽象成children
import { AnimatePresence, motion } from 'framer-motion'
import { cloneElement } from 'react'
import { useCollapseExpand } from './use-collapse-expand'
function CollapseExpand({ isShow, children }) {
const { slotRef, slotHeight } = useCollapseExpand(isShow)
return (
<AnimatePresence>
{isShow && (
<motion.div
style={{overflow:'hidden'}}
key="modal"
initial={{height:0}}
animate={{height:slotHeight}}
exit={{height:0}}
transition={{duration:0.3}}
>
{cloneElement(children, { ref: slotRef })}
</motion.div>
)}
</AnimatePresence>
)
}
export default CollapseExpand
传入ref的组件需要使用forwordRef
import { forwardRef } from 'react'
const Item = forwardRef(({ item }, ref) => {
return (
<section ref={ref}>
{...item}
</section>
)
})
export default Item
使用时只需要把需要收起展开的内容包起来,关心的变量也只有一个收起展开isShow
<CollapseExpand isShow={isRealShow}>
<Item item={item} />
</CollapseExpand>