# Usage with React
# In this tutorial your will learn how to use MoJS in React using React's Hooks API.
# Author: Jonas Sandstedt (opens new window)
Fork example on CodeSandbox (opens new window)
TLDR;
Assign your animation to an useRef
immutable object inside a useEffect
hook. Then you can use this ref to control the animation, like this: myRef.current.play()
# Install packages
Start by installing React, ReactDOM and @mojs/core from your favorite package manager. Here we use npm:
npm i react react-dom @mojs/core
# Create the animation component
Here is the final component that we're gonna build:
import React, { useRef, useEffect, useState, useCallback } from "react";
import mojs from "@mojs/core";
/**
* Usage:
* import MojsExample from './MojsExample';
*
* <MojsExample duration={1000}/>
*/
const MojsExample = ({ duration }) => {
const animDom = useRef();
const bouncyCircle = useRef();
const [isOpen, setIsOpen] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
// Prevent multiple instansiations on hot reloads
if (bouncyCircle.current) return;
// Assign a Shape animation to a ref
bouncyCircle.current = new mojs.Shape({
parent: animDom.current,
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: duration,
isShowStart: true,
easing: "elastic.inout",
onStart() {
setIsAnimating(true);
},
onComplete() {
setIsAnimating(false);
},
});
});
// Update the animation values when the prop changes
useEffect(() => {
if (!bouncyCircle.current) return;
bouncyCircle.current.tune({ duration: duration });
isOpen
? bouncyCircle.current.replayBackward()
: bouncyCircle.current.replay();
setIsOpen(!isOpen);
}, [duration]);
const clickHandler = useCallback(() => {
// If the "modal" is open, play the animation backwards, else play it forwards
isOpen ? bouncyCircle.current.playBackward() : bouncyCircle.current.play();
setIsOpen(!isOpen);
}, [isOpen]);
return (
<div ref={animDom} className="MojsExample">
<div className="content">
<h1>MoJS React Example</h1>
<p>Using hooks</p>
<button className="button" onClick={clickHandler}>
{isAnimating && isOpen ? "Animating" : isOpen ? "Close" : "Open"}
</button>
</div>
</div>
);
};
export default MojsExample;
# Let's break it down!
First we create a useRef
hooks, and takes advantage of its immutable object and assign our MoJS Shape
animation to it. This way we can later use it to control our animation:
const MojsExample = () => {
const bouncyCircle = useRef();
useEffect(() => {
bouncyCircle.current = new mojs.Shape({
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: 1000,
isShowStart: true,
easing: "elastic.inout",
});
});
return;
};
By checking if the ref already has been assigned, we can prevent the animation from being instantiated again on re-renders (very useful when using hot reloads):
const MojsExample = () => {
const bouncyCircle = useRef();
useEffect(() => {
if (bouncyCircle.current) return;
bouncyCircle.current = new mojs.Shape({
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: 1000,
isShowStart: true,
easing: "elastic.inout",
});
});
return;
};
To get the reference to DOM element we want to append our animation to, we can use a useRef
hook, and attach it to a container:
const MojsExample = () => {
const bouncyCircle = useRef();
const animDom = useRef();
useEffect(() => {
if (bouncyCircle.current) return;
bouncyCircle.current = new mojs.Shape({
parent: animDom.current,
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: 1000,
isShowStart: true,
easing: "elastic.inout",
});
bouncyCircle.current.play();
});
return <div ref={animDom} className="MojsExample" />;
};
export default MojsExample;
Now lets add a button to play the animation when we click it. To control the animation, we can now reference the MoJS animation using bouncyCircle.current.play();
const MojsExample = () => {
const bouncyCircle = useRef();
const animDom = useRef();
useEffect(() => {
if (bouncyCircle.current) return;
bouncyCircle.current = new mojs.Shape({
parent: animDom.current,
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: 1000,
isShowStart: true,
easing: "elastic.inout",
});
});
const clickHandler = useCallback(() => {
bouncyCircle.current.play();
});
return (
<div ref={animDom} className="MojsExample">
<button className="button" onClick={clickHandler}>
Play animation
</button>
</div>
);
};
export default MojsExample;
TIP
If we instead would want to play our animation directly, we could add bouncyCircle.current.play();
directly after the bouncyCircle declaration.
By passing props to our MojsExample
function, we can control the initial values of the animation, and also .tune()
the animation when they change.
/**
* Usage:
* import MojsExample from './MojsExample';
*
* <MojsExample duration={1000}/>
*/
const MojsExample = ({ duration }) => {
const animDom = useRef();
const bouncyCircle = useRef();
useEffect(() => {
if (bouncyCircle.current) return;
bouncyCircle.current = new mojs.Shape({
parent: animDom.current,
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: duration,
isShowStart: true,
easing: "elastic.inout",
});
});
useEffect(() => {
if (!bouncyCircle.current) return;
bouncyCircle.current.tune({ duration: duration });
bouncyCircle.current.replay();
}, [duration]);
const clickHandler = useCallback(() => {
bouncyCircle.current.play();
});
return (
<div ref={animDom} className="MojsExample">
<button className="button" onClick={clickHandler}>
Play animation
</button>
</div>
);
};
As a final touch, lets add some methods to listen animation events, and use Reacts useState to save it as a local state:
const MojsExample = ({ duration }) => {
const animDom = useRef();
const bouncyCircle = useRef();
const [isOpen, setIsOpen] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
if (bouncyCircle.current) return;
bouncyCircle.current = new mojs.Shape({
parent: animDom.current,
shape: "circle",
fill: { "#FC46AD": "#F64040" },
radius: { 50: 200 },
duration: duration,
isShowStart: true,
easing: "elastic.inout",
onStart() {
setIsAnimating(true);
},
onComplete() {
setIsAnimating(false);
},
});
});
useEffect(() => {
if (!bouncyCircle.current) return;
bouncyCircle.current.tune({ duration: duration });
isOpen
? bouncyCircle.current.replayBackward()
: bouncyCircle.current.replay();
setIsOpen(!isOpen);
}, [duration]);
const clickHandler = useCallback(() => {
// If the "modal" is open, play the animation backwards, else play it forwards
isOpen ? bouncyCircle.current.playBackward() : bouncyCircle.current.play();
setIsOpen(!isOpen);
}, [isOpen]);
return (
<div ref={animDom} className="MojsExample">
<button className="button" onClick={clickHandler}>
{isAnimating && isOpen ? "Animating" : isOpen ? "Close" : "Open"}
</button>
</div>
);
};
# Create the root App and render the page
Finally we import our component and add it to the root of our site.
import React from "react";
import ReactDOM from "react-dom";
import MojsExample from "./MojsExample";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<MojsExample duration={2000} />
</React.StrictMode>,
rootElement
);
You can see the full example and try it out here: CodeSandbox (opens new window).
There is also a button example (opens new window) with a Burst
animation using an object pooling array.
Usage with Server Side Rendering (SSR)
Note that this is a client-side library, and is not meant to be run on a server. So if you are using a library like Next.js, Gatsby, Nuxt.js or Angular Universal, make sure not to run your MoJS code on the server, just on the client side. How to do that differs from the library you are using. In React based libraries you can use the useEffect
hook or a dynamic import (read more here (opens new window)).
Happy animating!
← Burst Tools overview →