remotion-dev / remotion

🎥 Make videos programmatically with React
https://remotion.dev
Other
20.53k stars 1.03k forks source link

useCurrentFrame inside <Html/> #4077

Closed faelsoto closed 3 months ago

faelsoto commented 3 months ago

Not sure if this is a bug or not, but we're trying to do some animations inside a <ThreeCanvas> with html using drei's <Html> component, but the thing we're finding is that when we call useCurrentFrame we're getting this error:

useCurrentFrame() can only be called inside a component that was registered as a composition.

Initially we thought it was because <Html> needs to mount its elements somewhere, and by default it puts it in the root node, so we used its portal prop to make sure it's still inside the composition, but it still is not working and we're getting the same error.

Here's a reproduction script.

import React, { useRef } from "react";
import { Html } from "@react-three/drei";
import { Composition, interpolate, useCurrentFrame } from "remotion";
import { useVideoConfig } from "remotion";
import { ThreeCanvas } from "@remotion/three";

const Content = () => {
  // const frame = useCurrentFrame(); // this is triggering the error
  return <h1>hello</h1>;
};

const Box = ({ portalTarget }) => {
  const frame = useCurrentFrame();
  const rotation = interpolate(frame, [0, 1_000], [0, -5], {
    extrapolateRight: "clamp",
    extrapolateLeft: "clamp",
  });

  return (
    <group position={[0, 0.05, -0.75]} rotation={[0, rotation, 0]}>
      <mesh position={[0, 0.05, -0.7]}>
        <boxGeometry args={[2, 2, 0.1]} />
        <meshStandardMaterial color="#f00" />
        <Html transform portal={portalTarget}>
          <Content />
        </Html>
      </mesh>
    </group>
  );
};

const Scene = () => {
  const portalRef = useRef(null);

  const { width, height } = useVideoConfig();

  return (
    <div style={{ background: "rgba(0, 0, 0, 0.5)" }}>
      <div data-portal ref={portalRef}></div>
      <ThreeCanvas
        camera={{ position: [0, 2, 5], fov: 50 }}
        width={width}
        height={height}
      >
        <pointLight position={[10, 10, 10]} />
        <Box portalTarget={portalRef} />
      </ThreeCanvas>
    </div>
  );
};

export const RemotionRoot = () => {
  return (
    <>
      <Composition
        id="Scene"
        component={Scene}
        durationInFrames={100}
        fps={30}
        width={1280}
        height={720}
      />
    </>
  );
};

We've looked everywhere but it seems we're the only ones trying this stuff.

The way we've solved it so far is passing the frame from the Box to the Content component, that works but it feels hacky.

JonnyBurger commented 3 months ago

This is expected because the Html component is inside a separate React Reconciler or in any other way does not inherit the React Context of it's parent.

You can explicitly pass the Remotion Contexts to the child:

 const contexts = Internals.useRemotionContexts();

  return (
    <group position={[0, 0.05, -0.75]} rotation={[0, rotation, 0]}>
      <mesh position={[0, 0.05, -0.7]}>
        <boxGeometry args={[2, 2, 0.1]} />
        <meshStandardMaterial color="#f00" />

        <Html transform portal={portalTarget}>
          <Internals.RemotionContextProvider contexts={contexts}>
            <Content />
          </Internals.RemotionContextProvider>
        </Html>
      </mesh>
    </group>
  );

This is not officially supported and may break. Maybe we turn it into an official API once.