You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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不变,具体实现步骤:

    1. 将Logo所在的带背景色容器(bg-cream的div)改为<motion.div>,利用Framer-Motion的animate属性控制尺寸变化。
    2. 根据showNavBar状态定义两种尺寸状态:完整名称状态下的宽、内边距;收缩为单字母状态下的对应值(需根据首字母实际显示尺寸调整)。
    3. 配置过渡动画参数,确保容器尺寸变化和SVG内部字母动画的时长、缓动效果同步。

修改后的导航栏容器代码示例

// 导航栏父组件中替换原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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:09:56