A library for creating animated React components built on Theatre.js and Framer Motion.
Seamlessly integrate Theatre.js with Framer Motion and React and get the best of Theatre.js' animation sequencer and React's declarative API. Animate Framer Motion's motion values using Theatre.js, and have all the complexity like sheets, objects, animation instancing and wiring taken care of. Automatically get WYSIWYG editing tools with 1 line of code.
Luma.Dream.Machine.2024-06-21T16.25.52.mp4
While Theatre.js provides a framework-agnostic toolset, its concepts map directly to React and Framer Motion.
- Sheets -> React components
- Sheet instances -> React component instances
- Objects -> Framer Motion motion values
By codifying this interpretation, we can wrap Theatre.js' complexity in a simple declarative API that is very easy to read and write, integrates with Framer Motion's motion
components, and allows us to easily add powerful WYSIWYG editing tools to our components.
- Use Theatre.js with a simple declarative API.
- Animate Framer Motion's motion values using Theatre.js.
- Automatically create and manage sheet objects and sheet instances.
- Hold down the
Alt
key to display editable objects in dev mode. - Get WYSIWYG editing tools with 1 line of code.
FMT.Demo.2024-06-21T15.55.27.mp4
yarn add framer-motion-theatre framer-motion @theatre/core @theatre/studio
The following example demonstrates how to use Framer Motion Theatre. A working version can be found in the src
directory and run by cloning this repository and running yarn dev
.
// Wrap your component in withTheatre
const Box = withTheatre("Box", ({ color }: { color: string }) => {
// Returns an object of motion values you can plug into <motion.*> elements
const div = useSheetObject("div", {
width: 100,
height: 100,
// You can use Theatre's prop config finer control
scale: types.number(1, { nudgeMultiplier: 0.01 }),
});
const text = useSheetObject("text", {
content: "Click me!",
x: 0,
y: 0,
});
// Returns the controls associated with this component instance
const controls = useControls();
return (
<motion.div
// Use $studio.createGizmo to enable WYSIWYG tools for this element
ref={div.$studio.createGizmo()}
// Play the animation on click
onClick={() => {
controls.position = 0;
controls.play();
}}
// Just plug the returned values in here, zero fuss
style={{
...div,
}}
>
<motion.span ref={text.$studio.createGizmo()} style={{ ...text }}>
{/* You can also keyframe text by passing it as children */}
{text.content}
</motion.span>
</motion.div>
);
});
// Give it an instance ID and use it, FMT takes care of the rest
<Box instanceId="Box 1" color="#E493B3" />
<Box instanceId="Box 2" color="#EEA5A6" />
- Learn more about Theatre.js' powerful sequencer here.
- Hold down the
Alt
key to display editing tools.
Wrap your app in TheatreProvider
, passing it your Theatre.js project.
Optionally, pass in studio, or 'auto'
if you want it set up automatically in development.
<TheatreProvider project={project} studio="auto">
<App />
</TheatreProvider>
Wrap your components in withTheatre
to gain access to Framer Motion Theatre hooks and automatically set up sheets and objects for that component. The returned component has an extra mandatory prop instanceId
that must be unique for each instance of the component.
const MyComponent = withTheatre("MyComponent", () => {
// Your component here
});
<MyComponent instanceId="MyComponent 1" />;
<MyComponent instanceId="MyComponent 2" />;
Animate motion values using Theatre.js. Returns an object of motion values you can plug into motion.*
elements. Accepts an object of Theatre.js' prop types. Additionally, it returns an object with a $studio
property that allows you to enable WYSIWYG editing tools for this element.
const div = useSheetObject("div", {
width: 100,
height: 100,
scale: types.number(1, { nudgeMultiplier: 0.01 }),
});
return (
<motion.div
ref={div.$studio.createGizmo()}
style={{
...div,
}}
>
{/* ... */}
</motion.div>
);
The $studio
object returned by useSheetObject
allows you to enable WYSIWYG editing tools for the object and has the following properties:
Creates a gizmo for this element. Hold down the Alt
key to display selectable objects.
createGizmo()
accepts an object of options:
translate.x
: Accepts aMotionValue
returned byuseSheetObject
. It will edit the value when the gizmo is moved on the x axis.translate.y
: Accepts aMotionValue
returned byuseSheetObject
. It will edit the value when the gizmo is moved on the y axis.translate.strength
: A factor to multiply the translation by. Defaults to1
.zIndex
: The z-index of the gizmo. Defaults to0
. Gizmos are displayed on top of the page independently of your elements. ThezIndex
option lets you define the stacking order of gizmos. Higher values will be on top of lower values.ignoreComputedZIndex
: By default, the z-index assigned to gizmos will take into account the computed z-index of their corresponding element, which is what you want most of the time. In that case, the final z-index of the gizmo will be the value of thezIndex
option + the computed z-index of the element. If you want to ignore the computed z-index and only use thezIndex
option, set this totrue
.
const div = useSheetObject("div", {
width: 100,
height: 100,
});
return (
<motion.div
ref={div.$studio.createGizmo({
translate: {
x: div.width,
y: div.height,
},
})}
style={{
...div,
}}
>
{/* ... */}
</motion.div>
);
A boolean that indicates whether the object is currently selected.
const div = useSheetObject("div", {
width: 100,
height: 100,
});
if (div.$studio.isSelected) {
// ...
}
Allows you to select the object programmatically.
const div = useSheetObject("div", {
width: 100,
height: 100,
});
div.$studio.select();
Returns the controls associated with this animation instance. Learn more about Theatre.js' animation controls here.
const controls = useControls();
controls.play();
Returns the project and the studio instance associated with the component.
const { project, studio } = useTheatre();
useEffect(() => {
project.ready.then(() => {
// ...
});
}, [project]);