bitworking / react-scrollmagic

React components for ScrollMagic
https://bitworking.github.io/react-scrollmagic/
372 stars 64 forks source link

Use react context to access controller instance #33

Closed FreshlyBrewedCode closed 4 years ago

FreshlyBrewedCode commented 4 years ago

I've noticed that Scene components need to be a direct children of a Controller component to work properly. I think it would make sense to use the react context API to give any Scene component access to the nearest controller. This way you could also simply wrap everything inside a Controller and use Scenes at any point inside the app.

I've tried to build a custom version of the Controller that makes use of context but currently there does not seem to be a way to create an instance of the ScrollMagic.Controller class that is used in this library. Instead I used a workaround with a custom component that pretends to be a Scene and passes the controller prop on to the context:

type ScrollMagicContextType = {
    controller: any
}

const ScrollMagicContext = React.createContext<ScrollMagicContextType>(null);

export const ScrollMagicProvider: React.FC = ({ children }) => {

    return (
        <ReactScrollMagic.Controller>
            <ControllerGrabber>
                {controller => (
                    <ScrollMagicContext.Provider value={{ controller }}>
                        {children}
                    </ScrollMagicContext.Provider>
                )}
            </ControllerGrabber>
        </ReactScrollMagic.Controller>
    );
}

const ControllerGrabber = (props: { controller?: any, children: (ctr: any) => React.ReactNode }) => {
    return (
        <>
            {props.children(props.controller)}
        </>
    );
}

// Setting the display name to "Scene" causes the Controller to pass the controller instance as a prop
ControllerGrabber.displayName = "Scene";

Ideally the Scene component would simply consume the context. For now I use a simple hook and pass in the controller manually (you could also wirte a wrapper component for this):

export const useScrollController = () => {
    const scrollContext = useContext(ScrollMagicContext);
    return scrollContext.controller;
}

const SomeComponent = () => {
  const controller = useScrollController();

  return (
    <ReactScrollMagic.Scene
      {...{ controller }}
    >
        ...
    </ReactScrollMagic.Scene>
  );
}

Maybe this would be something to consider for a future release? Or is there a reason why context is not used? Also, like already mentioned in #22, I think Controller should pass the controller instance to every child and not just to Scene to avoid the displayName hack.

bitworking commented 4 years ago

Yes right, there is a PR open for this but I didn't had time to test it. But I recommend to use the ScrollTrigger with react-gsap anyway. I have an example in the repo. It has more features then ScrollMagic.

FreshlyBrewedCode commented 4 years ago

Sorry, didn't look through the PRs before posting. I will take a look at the example.