I have an SVG of a logo that, when on scroll, switches from the full name to just the initials. It sits in a container div with a colored background that should also change size accordingly. So far, I've been able to animate the extra characters out and move the initials to the correct space, but I'm having issues wrapping my mind on how to update/animate the container div's size.
I have it currently working using GSAP, but we're switching over to Framer Motion. It's a bit hard to explain, so here's the current working version: https://pixelbakery.com/about (I hope links are allowed)
Note: we use Tailwind as our styling framework.
My code as of now:
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)
}
// Create a window resize event listener
useEffect(() => {
setwindowHeight(window.innerHeight)
const handleWindowResize = () => {
setwindowHeight(window.innerHeight)
handleShowNavBar()
}
window.addEventListener('resize', handleWindowResize)
return () => {
window.removeEventListener('resize', handleWindowResize)
handleShowNavBar()
}
}, [])
// Create a scroll event listener, and call 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])
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>
</>
)
}
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}}
...
/>
{/* ESIGN */}
<motion.g
variants={fadeAway}
animate={showNavBar ? "show" : "hide"}
>
...
</motion.g>
{/* S */}
<motion.path
animate={showNavBar ? {{scale: 1}} : {{scale: 1.4, y:-10}}}
p='...'
/>
{/* TUDIO */}
<motion.g
variants={fadeAway}
animate={showNavBar ? "show" : "hide"}
>
...
</motion.g>
</motion.svg>
)
}
export default Nav_Logo;
Is changing the viewbox size even the best approach?
I don't know if this is useful, but it was fun to make. You cannot animate the viewBox of an SVG, but if you remove it all values in CSS will refer to the context document. Here I toggle the style small
on the SVG.
document.forms.f1.addEventListener('submit', e => {
e.preventDefault();
document.querySelector('svg').classList.toggle('small');
});
svg {
display: block;
width: 20em;
height: 7em;
transition: all 1s;
}
svg text {
dominant-baseline: middle;
text-anchor: middle;
font-size: 200%;
font-family: sans-serif;
font-weight: bold;
fill: #ff5e64;
transition: font-size .1s .3s;
}
svg text:nth-child(even) {
font-size: 270%;
}
tspan {
transition: fill-opacity .3s, font-size .1s .3s;
}
.small {
width: 6em;
}
.small .tail {
fill-opacity: 0;
font-size: 0;
}
.small text:nth-child(odd) {
font-size: 270%;
}
<svg>
<rect width="100%" height="100%" rx="5%" fill="#efe8f2" />
<text x="50%" y="40%">
<tspan class="head">p</tspan><tspan class="tail">ixel </tspan><tspan class="head">b</tspan><tspan class="tail">akery</tspan>
</text>
<text x="50%" y="70%" >
<tspan class="head">d</tspan><tspan class="tail">esign </tspan><tspan class="head">s</tspan><tspan class="tail">tudio</tspan>
</text>
</svg>
<form name="f1">
<button>toggle</button>
</form>