Introduction

Three.js is a Javascript library that lets us pierce into WebGL functionality in a easy way. For those of you who don’t know WebGL, it is a standard web specification with a Javascript API used to render 3D graphics. The creative potential is infinite, but it does have a steep learning curve. This is why there aren’t so many apps and websites that make the most out of these technologies.

To counter this, some efforts like Paul Henschel‘s react-spring and react-three-fiber tried to make WebGL implementation easier using abstraction layers that enable users to create React apps and include 3D graphics and animations easier than ever. This makes our code declarative and easy to read, as well as develop our app by re-using components. This is why I chose to give a shot at this and try to learn the basics.

Some words of advice before we begin: This is a basic demo I implemented in order to learn some basics, and I did not take the time to polish or correct some mistakes in the code. This was made while learning both react and these libraries so, everything you’ll see is a bunch of hacks and workarounds from a beginner.

Creating the 3D experience

In Three.js, everything that belongs to the 3D scene (“the scene” from now on) is incapsulated inside the Canvas component. Everything inside this component’s tags is being rendered each frame. This tag separated what belongs to the 3D world from the DOM’s HTML tags. I’ll show later on how can these two interact bewteen them.

Every 3D asset is loaded and rendered inside Canvas

It is possible to write declaratively everything the Three.js API offers. We just add the components and their corresponding imports. In the previous picture you can see we included an ambient light, a point light and some other controls that make the scene. React-three-fiber components are a direct translation from the Three.js ones. In fact, they’re actually the same since R3F is just a React reconciler for Three.js. It is possible to grab a reference to them with useRef hook and use them imperatively. Three.js’s docs are extensive and its API can be found on their official page.

Of course, we can create our own custom components and logic, but most typical recipes are already created and maintained by the community on this drei repository. We can find some interesting implementations such as hooks, camera controls, and so on which can be easily imported and used in our own projects to save some time. In fact, this is what I did.

Loading 3D models with React Suspense

The reccomended way of loading assets asyncronically in this kind of projects is by using the Suspense component. Libraries that support Suspense make it really easy to include it in our project. We can use it to fetch data while showing a fallback component. This way it makes it possible to render other parts of our app while data is being fetch without worrying about race conditions.

Suspense lets us fetch async data.

In the last image we load our Room component which internally executes a load operation (that can take some time) on a .gltf model. We put our loading indicator or custom code in the fallback component and pass it as a prop to Suspense.

The 3D model

This model was obtained through SketchFab and you can get it here. In order to include it in our project we can use one of the many recipes we talked about earlier. One of those is gltfjsx, which converts any .gltf model into a React component, allowing for editing, deletion, conditional rendering of individual groups and meshes and so much more.

gltfjsx creates a funcional component which loads the actual GLTF model

HTML and Three.js

It is possible to “include” HTML tags inside the Canvas. We achieve that with one of the drei recipes. We can handle CSS styles and pass props to them. The only limitation is that in order to show HTML inside our world we need to anchor the content to an empty mesh as shown below. This mesh takes a position prop.

Mesh component acts as an anchor for the HTML to the 3D scene

Animations in our 3D scene

Animating stuff in React can get complex and frame-by-frame debugging can become a nightmare quite fast. This is why Paul brings us a solution to this problem. Not only to us, but to everyone who whishes to include animations in their React projects. These animations are physics-based and they look great. (Though you can use classic animations with duration and easings as well)

React Spring is a library that supports both React and React Native, and works in pretty much any browser. It can be really easy to implement as you will see below. In the following example, we will be using our Marker component to demonstrate:

Defining animations (springs) in react-spring

First of all we need to define our springs. The useSpring hook asks for an initial value and a final value. These can be objets or pretty much whatever we need to animate. We declare the initial value in the from property and the final value in the to property. The config property lets us customize the way the animation looks. We can declare it with physics (tension, friction, mass, etc) or the classic way (duration, easing, etc). As you can see in the example it is also possible to conditionally change animation values based on props or state.

Next step is defining our components as animatable. For this we can either add the animated prefix to the compatible tags or use the animate function provided by react-spring to make any custom component an animatable component. This function returns an animatable component we can include declaratively in our code. These animatable components are animated outside of React.

Final step is, as shown in the image above, pass the props from our spring to our component. Each time state is updated the spring executes and passes the values to our animatable component. That’s it!

Conclusions

These libraries provide an easy way into WebGL for beginners like me and make it so much fun learning a new technology. Also, 3D graphics are very rewarding once you start getting the hang of it. With continuous community support I can see these libraries grow fast very soon. For the time being, you can clone this repo and check out the code here.

Share:

Leave a Reply

Your email address will not be published. Required fields are marked *