How to use R3F Next.js || Add page transitions with Framer Motion in the Canvas and DOM

Disclaimer: This is more a cheatsheet for me, but feel free to use it to understand it. All descriptions of files and functions are my understanding , if that makes sense. Take it with a grain of Salt. You also need some basic framer-motion knowledge. It’s also like a notebook with things noted that work and don’t work, as well as why. Big shout out to the 1 and only Oni creating this wonderful package in the first place👌
Let’s start with the File structure.
[src]
--^-- [components]
-------------^-- [canvas]
--------------------^-- [Shader]
---------------------------^-- [glsl]
---------------------------------^-- shader.frag
---------------------------------^-- shader.vert
---------------------------^-- Shader.jsx
------------------------ Box.jsx
-------------^-- [dom]
------------------^-- instruction.jsx-------------^-- [layout]
---------------------^-- canvas.jsx
---------------------^-- dom.jsx--^-- [helpers]
-----------^-- partition.js
-----------^-- store.js--^-- [pages]
---------^-- _app.jsx
---------^-- 404.jsx
---------^-- 500.jsx
---------^-- box.jsx
---------^-- index.jsx--^-- [styles]
---------^-- index.css--^-- config.jsx
Let’s check out some files sorted by their importance.
_app.jsx
_app.jsx is the fundament of our App. No matter which page, all informations that are set are available gloablly. E.g. if you have a navigation component with urls, it’ll be accesible on all subpages without them re-rendering.
index.jsx
index.jsx is the file we acces as our base url. Everything that is set there is only our startpage.
box.jsx
box.jsx will is a subpage that is accessible via _app.jsx (if you have a menu there) or on clicking the box inside index.jsx.
layout/canvas.jsx
Since this is nested in the _app.jsx file, it won’t change on a page transition. All children are dynamically added.
layout/dom.jsx
Same as Canvas.
config.jsx
All meta informations.
store.js
Stores values globally. Storing the router object there makes a lot of sense actually, we don’t have to call the router function in components.
I don’t know what dom does actually.
Start
Note how on a page switch the Shader cube disappers and the other one appears. It only changes the elements inside the layout component, so in order to have a page transition, we have to put all elements inside the layout component outside of index.jsx and box.jsx…
Either that, or as in our case we will take advantage of Framer Motion, which has a AnimatePresence property. That way we can change the page without have an abrupt change.
Ok yeah, let’s add <AnimatePresence initial={false}>
to our _app.jsx.


And a motion prop to the Shader mesh. Note that there is a framer-motion and framer-motion-3d package. 3D for the meshes. On top of that, add variants.
const variants = {
hidden: { opacity: 0, x: -2, y: 0 },
enter: { opacity: 1, x: 0, y: 0 },
exit: { opacity: 0, x: 0, y: -1 },
}
Should look something like this:


Note when switching pages, when the shader cube comes, it has a small animation. You can go ahead and clap on your shoulders now 🎉

The problem is that AnimatePresence is not working with framer-motion-3d. We’ll have to use the router prop shipped with next.js. We also have to check the disposing of objects guide in order to only load the model of the page.
Instead of working with pages, where the model is in, we just create pages. And when a page transitions, we check the path to get the correct model. This isn’t suitable for dynamic created pages for now. Maybe?
Let’s just replace the children props inside the canvas component and put the shader cube in there and check what happens. A router is already in place, so let’s use it to get the path.

I just did a useState and check how the router is doing.

Now Let’s see if we can check if the animation is finished. If yes, remove the mesh from the scene. I don’t know if that is a good performance option, but the most simple one of course.
Set an onAnimationComplete={onComplete} prop to the mesh. That way we see if the animation is finished.

In this tutorial we won’t get rid of the object. We can just make it invisible for performance reasons.
Removing something from your scene?
First of all, consider not doing that, especially if you will add it back again later. You can hide objects temporarily using
object.visible = false
(works for lights too), ormaterial.opacity = 0
. You can setlight.intensity = 0
to disable a light without causing shaders to recompile.
https://discoverthreejs.com/tips-and-tricks/#disposing-of-things
What happens when I call dispose() and then use the respective object at a later point?
The deleted internal resources will be created again by the engine. So no runtime error will occur but you might notice a negative performance impact for the current frame, especially when shader programs have to be compiled.
So, if animation is finished, just make the ref invisble. Easy!

Push the disabled prop a little outside the screen and we have a nice page transition.
