Hero background image
image (2)

This CMS application was built with Gatsby and Prismic. The checkout process is powered by Stripe and there is also extensive interaction with the Three.js JavaScrip library. Cloud hosting for this project is set up through Netlify.

react and three

Over the last couple of weeks, I’ve been in the process of revamping my personal site to coincide with the release of some new content. Here’s the final result. I’ve been really loving the look of three.js and was committed to integrating it into the site.

Gatsby

In order to build a site that could handle complex rendering, I chose a static site generator and Gatsby has been my go-to for its ease of use and flexibility with third-party plugins. Since Gatsby is powered by React I was at first a little unsure about how this library would be integrated. I love that React is an unopinionated framework, but you really need to have a solid understanding of their lifecycle methods so you can understand the order of operations. The way that we can work with the three.js library is by appending a canvas to the virtual DOM. Then we’re able to interact with it as you would any other dom element. Let’s take a look at two approaches to doing this.

Option One — Using packages and React hooks

This option utilizes the React Three Fiber package. Click on that link to get started with the docs or here is my starter code if you’d like to jump ahead to a working example.

After you clone down and start that repo you should see a 3D box that you can interact with and resize by clicking and dragging.

three js example

What I love about this package is that even though the canvas is still its own stand-alone element, we now have the capability to interact with the three.js library through state management. The way this is achieved is by returning the mesh method from three.js, which has been appended onto the animation attribute from React Spring. Having this setup means we can dynamically affect different attributes of the library with the inbuild mouse events.

const Box = () => {
  const [hovered, setHovered] = useState(false)
  const [active, setActive] = useState(false)
  const props = useSpring({
    scale: active ? [1.5, 1.5, 1.5] : [1, 1, 1],
    color: hovered ? "green" : "blue",
  })

  return (
    <a.mesh
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      onClick={() => setActive(!active)}
      scale={props.scale}
      castShadow
    >
      <boxBufferGeometry attach="geometry" args={[1, 1, 1]} />
      <a.meshPhysicalMaterial attach="material" color={props.color} />
    </a.mesh>
  )
}

With this option, we are appending the canvas to our application with the useRef hook, which lets us create mutable variables inside functional components. If you’ve never used React refs before they are a great way to access DOM nodes or React elements, but it must be done within a functional component.

Options two — a lighter package free approach

The second approach I found and actually the method I ended up using doesn’t use any third-party packages except three.js.

three js example 2

This time our entry point will be a class component inside the componetDidMount lifecycle method.

import React, { Component } from 'react';
import styled from '@emotion/styled';
import threeEntryPoint from '../components/findTheDay/threeEntryPoint';

const ThreeContainer = styled.div`
`
export default class FindTheDayCanvas extends Component {

  componentDidMount() {
    threeEntryPoint(this.threeRootElement);
  }

  render() {

    return (
      <ThreeContainer>
        <div
          className="header-header"
          ref={element => (this.threeRootElement = element)}
        />
      </ThreeContainer>
    );
  }
}

Here’s what your canvas manager could look like:

import SceneManager from './SceneManager';

export default container => {
    const canvas = createCanvas(document, container);
    const sceneManager = new SceneManager(canvas);

    let canvasHalfWidth;
  let canvasHalfHeight;

    bindEventListeners();
    render();

    function createCanvas(document, container) {
        const canvas = document.createElement('canvas');     
        container.appendChild(canvas);
        return canvas;
    }

    function bindEventListeners() {
        window.onresize = resizeCanvas;
        window.onmousemove = mouseMove;
        resizeCanvas();	
    }

    function resizeCanvas() {        
        canvas.style.width = '100%';
        canvas.style.height= '100%';
        
        canvas.width  = canvas.offsetWidth;
        canvas.height = canvas.offsetHeight;

        canvasHalfWidth = Math.round(canvas.offsetWidth/2);
        canvasHalfHeight = Math.round(canvas.offsetHeight/2);

        sceneManager.onWindowResize()
    }

    function mouseMove({screenX, screenY}) {
        sceneManager.onMouseMove(screenX-canvasHalfWidth, screenY-canvasHalfHeight);
    }

  function render(time) {

        requestAnimationFrame(render);
        sceneManager.update();
    }
}

I really like this approach because it makes our code more modular and organized. The next steps would be to create your subject matter and then lighting.

Working with three.js is really exciting. There is, however, a substantial amount to take in given the nature of 3D graphics and the intricacies they involve. I’ve just covered a couple of ways to get you started within a React setting, but to get something truly awesome on the screen I’d recommend having a solid understanding of this library and its capabilities by checking out the official docs.

alt

alt

alt

alt

alt

site logo