framer-motion-3d
Version:
A simple and powerful React animation library for @react-three/fiber
112 lines (109 loc) • 4.54 kB
JavaScript
"use client";
import { jsx } from 'react/jsx-runtime';
import { createRoot, events, unmountComponentAtNode } from '@react-three/fiber';
import { MotionContext, MotionConfigContext, useForceUpdate, useIsomorphicLayoutEffect, clamp } from 'framer-motion';
import * as React from 'react';
import { forwardRef, useContext, useRef, useLayoutEffect } from 'react';
import { mergeRefs } from 'react-merge-refs';
import { MotionCanvasContext } from './MotionCanvasContext.mjs';
const devicePixelRatio = typeof window !== "undefined" ? window.devicePixelRatio : 1;
const calculateDpr = (dpr) => Array.isArray(dpr)
? clamp(dpr[0], dpr[1], devicePixelRatio)
: dpr || devicePixelRatio;
/**
* This file contains a version of R3F's Canvas component that uses our projection
* system for layout measurements instead of use-react-measure so we can keep the
* projection and cameras in frame.
*
* https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/web/Canvas.tsx
*/
function Block({ set }) {
useIsomorphicLayoutEffect(() => {
set(new Promise(() => null));
return () => set(false);
}, []);
return null;
}
class ErrorBoundary extends React.Component {
constructor() {
super(...arguments);
this.state = { error: false };
}
componentDidCatch(error) {
this.props.set(error);
}
render() {
return this.state.error ? null : this.props.children;
}
}
ErrorBoundary.getDerivedStateFromError = () => ({ error: true });
function CanvasComponent({ children, fallback, tabIndex, id, style, className, events: events$1, ...props }, forwardedRef) {
/**
* Import existing contexts to pass through variants and MotionConfig from
* the DOM to the 3D tree. Shared variants aren't officially supported yet
* because the parent DOM tree fires effects before the 3D tree, whereas
* variants are expected to run from bottom-up in useEffect.
*/
const motionContext = useContext(MotionContext);
const configContext = useContext(MotionConfigContext);
const [forceRender] = useForceUpdate();
const layoutCamera = useRef(null);
const dimensions = useRef({
size: { width: 0, height: 0 },
});
const { size, dpr } = dimensions.current;
const containerRef = useRef(null);
const handleResize = () => {
const container = containerRef.current;
dimensions.current = {
size: {
width: container.offsetWidth,
height: container.offsetHeight,
},
};
forceRender();
};
// Set canvas size on mount
useLayoutEffect(handleResize, []);
const canvasRef = React.useRef(null);
const [block, setBlock] = React.useState(false);
const [error, setError] = React.useState(false);
// Suspend this component if block is a promise (2nd run)
if (block)
throw block;
// Throw exception outwards if anything within canvas throws
if (error)
throw error;
const root = useRef(null);
if (size.width > 0 && size.height > 0) {
if (!root.current) {
root.current = createRoot(canvasRef.current);
}
root.current.configure({
...props,
dpr: dpr || props.dpr,
size: { ...size, top: 0, left: 0 },
events: events$1 || events,
}).render(jsx(ErrorBoundary, { set: setError, children: jsx(React.Suspense, { fallback: jsx(Block, { set: setBlock }), children: jsx(MotionCanvasContext.Provider, { value: {
dimensions,
layoutCamera,
requestedDpr: calculateDpr(props.dpr),
}, children: jsx(MotionConfigContext.Provider, { value: configContext, children: jsx(MotionContext.Provider, { value: motionContext, children: children }) }) }) }) }));
}
useIsomorphicLayoutEffect(() => {
const container = canvasRef.current;
return () => unmountComponentAtNode(container);
}, []);
return (jsx("div", { ref: containerRef, id: id, className: className, tabIndex: tabIndex, style: {
position: "relative",
width: "100%",
height: "100%",
overflow: "hidden",
...style,
}, children: jsx("canvas", { ref: mergeRefs([canvasRef, forwardedRef]), style: { display: "block" }, children: fallback }) }));
}
/**
* @deprecated Motion 3D is deprecated.
*/
const MotionCanvas = forwardRef(CanvasComponent);
export { MotionCanvas };