In/Out Animations
So far we've managed to send dynamic text to our graphic and making sure it fades in/out using simple CSS transitions. You can get pretty far with this, but at some point you'll most likely reach for an animation library to create more complex animations.
There are a lot of options to choose from, and they all have their pros and cons. We typically reach for Framer Motion (opens in a new tab) for most use cases, but sometimes utilize GSAP (opens in a new tab) for orchestrating animations in a timeline or to animate characters, words or lines individually. Here we'll show how you can create the following in/out animation using Framer Motion:
Here's a breakdown of what we're seeing above:
- The white box is positioned 20px below the bottom of the screen with an opacity of 0.
- When it receives
play
, it moves up to its original position during 600 ms, while also fading in during 400 ms. - After 300 ms, the text
Framer Motion
We'll start by converting our div into a motion.div
(opens in a new tab),
and wrapping it inside a FramerMotion component:
import { render, useCaspar, FramerMotion } from '@nxtedition/graphics-kit'
import { motion } from 'framer-motion'
function Example() {
const { data } = useCaspar()
return (
<FramerMotion>
<motion.div
style={{
position: 'absolute',
bottom: 80,
left: 266,
width: 1388,
padding: 20,
backgroundColor: 'white',
borderRadius: 6,
fontSize: 70,
fontFamily: 'Arial',
overflow: 'hidden'
}}
>
{data.text}
</motion.div>
</FramerMotion>
)
}
render(Example)
This gives our div
super powers, allowing us to easily add animations to it. <FramerMotion>
also
takes care of calling safeToRemove()
once we're done animating.
Now we can tell it what it should look like before it receives play
using the initial
prop:
initial={{ opacity: 0, y: 100 }}
Next we can tell it what it should look like after it receives the play
command using the animate
prop:
animate={{
opacity: 1,
y: 0,
transition: {
opacity: {
duration: 0.4
},
y: {
duration: 0.6
}
}
}}
And finally what should happen when it receives stop
using the exit
prop:
exit={{
y: 100,
opacity: 0,
transition: {
duration: 0.4
}
}}
Putting it all together we get this:
import { render, useCaspar, FramerMotion } from '@nxtedition/graphics-kit'
import { motion } from 'framer-motion'
function Example() {
const { data } = useCaspar()
return (
<FramerMotion>
<motion.div
style={{
position: 'absolute',
bottom: 80,
left: 266,
width: 1388,
padding: 20,
backgroundColor: 'white',
borderRadius: 6,
fontSize: 70,
fontFamily: 'Arial',
overflow: 'hidden'
}}
initial={{
opacity: 0,
y: 100
}}
animate={{
opacity: 1,
y: 0,
transition: {
opacity: {
duration: 0.4
},
y: {
duration: 0.6
}
}
}}
exit={{
y: 100,
opacity: 0,
transition: {
duration: 0.4
}
}}
>
{data.text}
</motion.div>
</FramerMotion>
)
}
render(Example)
This will animate our white background properly, but if you looked closely at the animation above
you might have noticed that the text has its own animation, with a slight delay. Let's fix that by
wrapping the text in its own <motion.div>
:
import { render, useCaspar, FramerMotion } from '@nxtedition/graphics-kit'
import { motion } from 'framer-motion'
function Example() {
const { data } = useCaspar()
return (
<FramerMotion>
<motion.div
style={{
position: 'absolute',
bottom: 80,
left: 266,
width: 1388,
padding: 20,
backgroundColor: 'white',
borderRadius: 6,
fontSize: 70,
fontFamily: 'Arial',
overflow: 'hidden'
}}
initial={{
opacity: 0,
y: 100
}}
animate={{
opacity: 1,
y: 0,
transition: {
opacity: {
duration: 0.4
},
y: {
duration: 0.6
}
}
}}
exit={{
y: 100,
opacity: 0,
transition: {
duration: 0.4
}
}}
>
<motion.div
initial={{
opacity: 0,
y: '100%'
}}
animate={{
opacity: 1,
y: 0,
transition: {
duration: 0.3,
delay: 0.3
}
}}
>
{data.text}
</motion.div>
</motion.div>
</FramerMotion>
)
}
render(Example)
And we're done! 🎉 We now have a graphic that can receive dynamic data and that animates in and out nicely as we send play
and stop
commands.
The only thing left before we can use it in CasparCG is to build it.