Info
This chapter integrates Five SDK in a React project using the ClassComponent + HOC pattern. Full source examples: JavaScript | TypeScript.
In this chapter you will learn
- How to set up your development environment.
- How to import the Five SDK.
- How to render a 3D space on screen.
Preparation
Development environment
- You need a modern browser.
Info
Five SDK supports the following browsers. Pick whichever one you are comfortable with:
| Safari | Safari on iOS | Chrome | Chrome for Android | Edge | Firefox |
|---|---|---|---|---|---|
| >= 9 | >= 9 | >= 49 | >= 93 | >= 13 | >= 45 |
- Install Node.js
^20.19.0 or >=22.12.0, matching the Vite 8 engine requirement used by this guide.
Using a build tool
This example initializes the development environment with Vite. You can bootstrap it yourself with the code below.
# npm 6.x
npm create vite@9.1.1 my-react-app --template react
# npm 7+, extra double-dash is needed:
npm create vite@9.1.1 my-react-app -- --template react # npm 6.x
npm create vite@9.1.1 my-react-app --template react-ts
# npm 7+, extra double-dash is needed:
npm create vite@9.1.1 my-react-app -- --template react-tsUnder src, create the directory src/0.getting-started for this tutorial.
Every tutorial creates a new directory to keep its work, which makes it easier to summarize and look things up. When the course is finished you will end up with:
src
├── 0.getting-started
├── 1.displaying-work
├── 2.knowing-state
...
a directory structure like this. The complete code samples follow the same structure, so you can refer to them at any time.
Tip
If you are familiar with other build tools such as Webpack, Snowpack, or parcel, you can use them instead.
Creating the HTML file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Getting Started</title>
<!-- highlight-start -->
<style>
* { margin: 0; padding: 0; }
html, body, #app { width: 100%; height: 100%; overflow: hidden; }
</style>
<!-- highlight-end -->
</head>
<body>
<!-- highlight-start -->
<div id="app"></div>
<script type="module" src="./index"></script>
<!-- highlight-end -->
</body>
</html>Referencing the entry directly with <script type="module" src="./index"></script> is a Vite feature. If you use another build tool, handle the code import and entry file yourself — for example, use HtmlWebpackPlugin with webpack.
Writing the test logic
Let's start with a simple
Hello Worldto make sure the whole pipeline runs.
const app = document.querySelector("#app");
// highlight-start
app.innerHTML = "Hello World.";
// highlight-end
export {};const app = document.querySelector("#app")!;
// highlight-start
app.innerHTML = "Hello World.";
// highlight-end
export {};The trailing export {}; is required because Vite imports the file with type="module", so every file needs to be a module and therefore needs an export. If you use another build tool, write it according to that tool's requirements.
Start the dev server with npm run dev, then open the current page at "http://localhost:3000/src/0.getting-started/index.html".
Info
Check your console: the port number may change depending on your configuration and which ports are currently in use, so rely on the console output as the source of truth. If you use another build tool, start the server according to that tool's requirements.
You will then see the output
Hello World.on the page, which means the build tool is set up correctly.
The following chapters will not describe the steps above in detail; just complete them the same way.
Installing dependencies from npm
Install the dependencies in your project directory.
npm install @realsee/five@6.8.9 three@0.117.1 react react-dom @wordpress/composeRequired dependencies:
- @realsee/five Five SDK
- three three.js, the graphics/math library that Five SDK depends on. Use the exact
0.117.1version. - react the React framework
- react-dom React's browser renderer
- @wordpress/compose the HOC composition helper for React HOCs
npm install @realsee/five@6.8.9 three@0.117.1 react react-dom @types/react @types/react-dom @wordpress/composeRequired dependencies:
- @realsee/five Five SDK
- three three.js, the graphics/math library that Five SDK depends on. Use the exact
0.117.1version. - react the React framework
- react-dom React's browser renderer
- @types/react TypeScript type declarations for the React framework
- @types/react-dom TypeScript type declarations for React's browser renderer
- @wordpress/compose the HOC composition helper for React HOCs
Rendering a 3D space
It's time to render a VR scene and take a look.
Loading the 3D space
Delete your previous Hello World code; we'll start over. You don't need to understand the meaning of the code below just yet — you will learn it in the next chapter.
import React, { Component, ComponentClass } from "react";
import ReactDOM from "react-dom";
import { Work, parseWork } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork(url) {
return function(Compnent) {
return class extends Component {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL)
)(class extends Component {
render() {
const { work } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={512} height={512}/>
</FiveProvider>;
}
});
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};import React, { Component, ComponentClass } from "react";
import ReactDOM from "react-dom";
import { Work, parseWork } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork<P extends Record<string, any>>(url: string) {
return function(Compnent: ComponentClass<P & { work: Work }>): ComponentClass<P> {
return class extends Component<P, {work: Work | null}> {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL)
)(class extends Component<{work: Work}> {
render() {
const { work } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={512} height={512}/>
</FiveProvider>;
}
})
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};Go back to your browser and check whether a 3D space is now displayed in the top-left corner. You can drive the view with your mouse or touch gestures — the basic browsing features are already included.
Making the view fill the whole screen
You may not fully understand how the code above works yet, but you can see that FiveCanvas has width and height props, which look a lot like the canvas dimensions. Viewing it in the browser, the view sits in the top-left corner, which confirms the guess. That's right — they are used to set the canvas dimensions.
So let's use the React Hooks approach we're familiar with to make it fill the screen.
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { parseWork } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork(url) {
return function(Compnent) {
return class extends Component {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions() {
return function(Compnent) {
return class extends Component {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(
class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
})
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};import React, { Component, ComponentClass } from "react";
import ReactDOM from "react-dom";
import { Work, parseWork } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork<P extends Record<string, any>>(url: string) {
return function(Compnent: ComponentClass<P & { work: Work }>): ComponentClass<P> {
return class extends Component<P, {work: Work | null}> {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions<P extends Record<string, any>>() {
return function(Compnent: ComponentClass<P & { windowDimensions: { width: number, height: number} }>): ComponentClass<P> {
return class extends Component<P, {width: number, height: number}> {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
})
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};Go back to your browser and check whether it looks as expected.
Nice work 🥳 !
Reorganizing and splitting the code
Right now all of our code lives in a single js / ts file. While you can see all the logic at a glance, it's a bit messy. Splitting the content across multiple files will improve this.
- Split
Appinto its own file. - Split the
withFetchWorkfunction into its own file. - Split the
withWindowDimensionsfunction into its own file.
src/0.getting-started/withFetchWork.jsx
import React, { Component } from "react";
import { parseWork } from "@realsee/five";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork(url) {
return function(Compnent) {
return class extends Component {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };src/0.getting-started/withWindowDimensions.jsx
import React, { Component } from "react";
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions() {
return function(Compnent) {
return class extends Component {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };src/0.getting-started/App.jsx
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
});
export { App };src/0.getting-started/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};src/0.getting-started/withFetchWork.tsx
import React, { Component, ComponentClass } from "react";
import { Work, parseWork } from "@realsee/five";
/**
* React HOC that fetches the work
* @param url the URL of work.json
*/
function withFetchWork<P extends Record<string, any>>(url: string) {
return function(Compnent: ComponentClass<P & { work: Work }>): ComponentClass<P> {
return class extends Component<P, {work: Work | null}> {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };src/0.getting-started/withWindowDimensions.tsx
import React, { Component, ComponentClass } from "react";
/**
* React HOC: gets the current window dimensions
*/
function withWindowDimensions<P extends Record<string, any>>() {
return function(Compnent: ComponentClass<P & { windowDimensions: { width: number, height: number} }>): ComponentClass<P> {
return class extends Component<P, {width: number, height: number}> {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };src/0.getting-started/App.tsx
import React, { Component } from "react";
import { Work } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
/** Data URL of work.json */
const workURL = "https://vr-public.realsee-cdn.cn/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
</FiveProvider>;
}
});
export { App };src/0.getting-started/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};Much more comfortable, isn't it? Every file is now concise, and it's easy to understand what each one does.
What you will learn in the next chapter
In the next chapter you will learn
- What a Work is.
- How the code we just wrote works — for example, how the
FiveProvider/FiveCanvascomponents work.
