如何用Framer-Motion实现SVG viewBox及容器尺寸滚动动画?
问题:滚动时Logo从完整名称切换为首字母,容器尺寸同步动画(Framer-Motion + Tailwind)
我有一个Logo SVG,页面滚动时需要从完整名称切换为仅显示首字母,其所在带背景色的容器div尺寸也要同步变化。目前已经实现了多余字符淡出及首字母位移动画,但不知道怎么用Framer-Motion实现容器尺寸的更新与动画。之前用GSAP实现过效果,现在切换到Framer-Motion(搭配Tailwind样式框架),以下是我的代码:
1. 滚动位置计算代码(上下文参考)
const [isHamActive, setHamToggle] = useState(false) const [windowHeight, setwindowHeight] = useState(0) const [showNavBar, setShowNavBar] = useState(true) const [scrollPosition, setScrollPosition] = useState(0) function handleShowNavBar() { if (scrollPosition + 1 >= windowHeight / 3) setShowNavBar(false) else setShowNavBar(true) } // 窗口 resize 监听 useEffect(() => { setwindowHeight(window.innerHeight) const handleWindowResize = () => { setwindowHeight(window.innerHeight) handleShowNavBar() } window.addEventListener('resize', handleWindowResize) return () => { window.removeEventListener('resize', handleWindowResize) handleShowNavBar() } }, []) // 滚动监听 useEffect(() => { setScrollPosition(window.scrollY + 1) handleShowNavBar() const handleScroll = () => { const position = window.scrollY + 1 setScrollPosition(position) handleShowNavBar() } window.addEventListener('scroll', handleScroll, { passive: true }) return () => { window.removeEventListener('scroll', handleScroll) } }, [scrollPosition])
2. 导航栏父组件代码
import { motion, Variants } from 'framer-motion' import Nav_Logo from './Nav_Logo' export default function Navbar() { const navItem: Variants = { offscreen: (delay) => ({ y: -300, transition: { ease: 'easeOut', duration: 1, delay: delay, }, }), onscreen: (delay) => ({ y: 0, transition: { ease: 'easeOut', duration: 1, delay: delay, }, }), } return ( <> <motion.div initial='offscreen' className={'z-40 '}> <motion.div initial={'offscreen'} animate={'onscreen'} variants={navItem} custom={0.3} className='navItem origin-top-left ml-8 mt-8 fixed top-0 left-0 z-40 ' > <div className='bg-cream rounded-md origin-top-left hidden xl:block '> <Link hrefLang={'en-US'} href={'/'} className=' pointer-events-auto block relative min-w-full z-40 px-4 pt-3 my-0 w-full' > <Nav_Logo showNavBar={showNavBar} /> </Link> </div> </motion.div> </motion.div> </> ) }
3. SVG Logo组件代码
import { Variants, motion } from "framer-motion"; const fadeAway: Variants = { hide: (delay) => ({ opacity: 0, transition: { ease: "easeOut", duration: 1, delay: delay, }, }), show: (delay) => ({ opacity: 1, transition: { ease: "easeOut", duration: 1, delay: delay, }, }), }; const Nav_Logo = ({ showNavBar }) => { return ( <motion.svg xmlns='http://www.w3.org/2000/svg' version='1.1' id='Logo_Wordmark' viewBox='0 0 494 138' width={"100%"} preserveAspectRatio='xMaxYMin meet' > {/* P */} <motion.path d='...' /> {/* IXEL */} <motion.g variants={fadeAway} animate={showNavBar ? "show" : "hide"} > ... </motion.g> {/* B */} <motion.path animate={showNavBar ? {x: 0} : {x: -140}} d='...' /> {/* AKERY */} <motion.g variants={fadeAway} animate={showNavBar ? "show" : "hide"} > ... </motion.g> {/* D */} <motion.path animate={showNavBar ? {scale: 1} : {scale: 1.4, y:-10}} d='...' /> {/* ESIGN */} <motion.g variants={fadeAway} animate={showNavBar ? "show" : "hide"} > ... </motion.g> {/* S */} <motion.path animate={showNavBar ? {scale: 1} : {scale: 1.4, y:-10}} d='...' /> {/* TUDIO */} <motion.g variants={fadeAway} animate={showNavBar ? "show" : "hide"} > ... </motion.g> </motion.svg> ) } export default Nav_Logo;
疑问:修改SVG的viewbox尺寸是否为实现该需求的最佳方案?
回答
修改SVG的viewbox不是最佳方案。viewbox变动会直接改变SVG视口范围,容易和已实现的首字母位移、缩放动画产生冲突,导致首字母定位或缩放出现意外偏移,大幅增加调试成本。
更稳妥的方案是用Framer-Motion直接动画容器的尺寸属性,同时保持SVG的viewbox不变,具体实现步骤:
- 将Logo所在的带背景色容器(
bg-cream的div)改为<motion.div>,利用Framer-Motion的animate属性控制尺寸变化。 - 根据
showNavBar状态定义两种尺寸状态:完整名称状态下的宽、内边距;收缩为单字母状态下的对应值(需根据首字母实际显示尺寸调整)。 - 配置过渡动画参数,确保容器尺寸变化和SVG内部字母动画的时长、缓动效果同步。
- 将Logo所在的带背景色容器(
修改后的导航栏容器代码示例
// 导航栏父组件中替换原div为motion.div <motion.div className='bg-cream rounded-md origin-top-left hidden xl:block' // 根据showNavBar状态切换尺寸 animate={{ width: showNavBar ? 'auto' : '80px', paddingLeft: showNavBar ? '16px' : '12px', paddingRight: showNavBar ? '16px' : '12px', paddingBottom: showNavBar ? '3px' : '8px' }} // 和SVG内部动画保持一致的过渡参数 transition={{ ease: 'easeOut', duration: 1 }} > <Link hrefLang={'en-US'} href={'/'} className='pointer-events-auto block relative min-w-full z-40 px-4 pt-3 my-0 w-full' > <Nav_Logo showNavBar={showNavBar} /> </Link> </motion.div>
SVG组件的优化建议
保持SVG的viewbox='0 0 494 138'不变,因为你已经通过motion.path给首字母做了位移、缩放动画,固定视口能让首字母的显示位置更可控,避免viewbox变动带来的额外适配问题。
内容的提问来源于stack exchange,提问作者JordyJordyJordyJordan
相关产品推荐
相关产品推荐

