Enhance React Animations: A Guide to the View Transitions API
I recreated the Feedback component from this tweet by Emil Kowalski without using framer motion.
Animations are made with view transitions api so it's not supported in all browsers yet.
Let's break down parts of the component to understand how it works.
Initial Component
First we need to create the component that will change the state and conditionally render the button, form or success message.
export function FeedbackInitialButton() {
const [state, setState] = useState<"initial" | "open" | "success">("initial");
function nextState() {
setState((state) => {
if (state === "initial") return "open";
if (state === "open") return "success";
return "initial";
});
}
return (
<div >
{state === "initial" && <InitialButton nextState={nextState} />}
{state === "open" && <FeedbackForm nextState={nextState} />}
{state === "success" && <SuccessAlert nextState={nextState} />}
</div>
);
}
Adding Default Transitions
Then we add the transitions to the component using the view transitions API.
To add the transitions we need to call document.startViewTransition
and inside it make the changes to the state. The changes are made inside a flushSync
to make sure the changes are applied synchronously.
I created a helper function to wrap the transition logic:
function makeTransition(transition: () => void) {
// Check if the browser supports the view transitions API
// if not, just call the transition
if (document.startViewTransition) {
document.startViewTransition(() => {
flushSync(() => {
transition();
});
});
} else {
transition();
}
}
Then we can use it to wrap the state changes:
function nextState() {
makeTransition(() => {
setState((state) => {
if (state === "initial") return "open";
if (state === "open") return "success";
return "initial";
});
});
}
This component looks great, but it's only animating a crossfade between states. We can make it more complex.
More Transitions
Grow Animation
We can animate parts of the component separately. For example, by setting the "view-transition-name" attribute to the same value in both the button and the form background, the button will "grow" into the form.
In this example, I put the view transition name in the button style prop, but you can also use a CSS class.
<button
onClick={nextState}
style={{
viewTransitionName: "bg-grow",
}}
>
Open
</button>
And in the form component:
<div
className="bg-gray-100"
style={{
viewTransitionName: "bg-grow",
}}
>
<div className="bg-white">
<form>
/* rest of the form */
</form>
</div>
</div>
Text Animation
View transitions allow us to animate parts of the component separately. We can use this to animate the text in the button and the background separately.
I put the button text in a span so I can animate it separately from the button.
<button
style={{
viewTransitionName: "bg-grow",
}}
>
<span
style={{
viewTransitionName: "text",
}}
>
Feedback
</span>
</button>
I also put a span as a placeholder in the form to animate the text separately.
<div
className="bg-gray-100"
style={{
viewTransitionName: "bg-grow",
}}
>
<div className="bg-white">
<form>
<span
style={{
viewTransitionName: "text",
}}
aria-hidden
>
Feedback
</span>
/* rest of the form */
</div>
</div>
The Great Thing About View Transitions
At this point, you can see how powerful view transitions are and we didn't even start using the pseudo-elements of the view transitions API that allow us to animate things like the blur in the success state of the component.
I hope this example helps you understand how to use the view transitions API with React components. If you have any questions, feel free to contact me.