├── .gitignore
├── .vscode
└── settings.json
├── src
├── __tests__
│ ├── __mocks__
│ │ └── fileMock.js
│ ├── __setup__
│ │ └── enzyme.js
│ ├── __snapshots__
│ │ └── App.jsx.snap
│ └── App.jsx
├── index.js
├── App.jsx
├── index.html
├── index.css
├── VisWithClass.jsx
└── VisWithHooks.jsx
├── CHANGELOG.md
├── .babelrc
├── .eslintrc
├── README.md
├── webpack.config.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/src/__tests__/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'file-mock'
2 |
--------------------------------------------------------------------------------
/src/__tests__/__setup__/enzyme.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme'
2 | import Adapter from 'enzyme-adapter-react-16'
3 |
4 | configure({ adapter: new Adapter() })
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v1.2.0
2 |
3 | - Added Jest integration
4 | - Added support for `async` / `await`
5 |
6 | ## v1.1.0
7 |
8 | - Upgrade to Babel 7 (using `npx babel-upgrade --write`)
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import App from './App'
5 |
6 | import './index.css'
7 |
8 | ReactDOM.render(, document.getElementById('app'))
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-object-rest-spread",
5 | "@babel/plugin-proposal-class-properties",
6 | "@babel/plugin-transform-runtime"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // import Vis from './VisWithClass'
4 | import Vis from './VisWithHooks'
5 |
6 | const App = () => (
7 |
{
74 | this.mount = mount
75 | }}
76 | />
77 | )
78 | }
79 | }
80 |
81 | export default VisWithClass
82 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-with-threejs",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "webpack-dev-server --mode development --open",
7 | "dev": "webpack --mode development",
8 | "build": "webpack --mode production",
9 | "lint": "eslint ./src",
10 | "fix": "eslint ./src --fix",
11 | "test": "jest",
12 | "deploy": "gh-pages -d dist"
13 | },
14 | "prettier": {
15 | "semi": false,
16 | "singleQuote": true,
17 | "trailingComma": "all"
18 | },
19 | "jest": {
20 | "roots": [
21 | "./src"
22 | ],
23 | "setupFiles": [
24 | "./src/__tests__/__setup__/enzyme.js"
25 | ],
26 | "testPathIgnorePatterns": [
27 | "/node_modules/",
28 | "/__mocks__/",
29 | "/__setup__/"
30 | ],
31 | "moduleNameMapper": {
32 | "^.+\\.(css|svg)$": "
/src/__tests__/__mocks__/fileMock.js"
33 | }
34 | },
35 | "author": "Will Bamford",
36 | "license": "MIT",
37 | "private": false,
38 | "devDependencies": {
39 | "@babel/core": "^7.0.0",
40 | "@babel/plugin-proposal-class-properties": "^7.1.0",
41 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
42 | "@babel/plugin-transform-runtime": "^7.1.0",
43 | "@babel/preset-env": "^7.0.0",
44 | "@babel/preset-react": "^7.0.0",
45 | "babel-core": "^7.0.0-bridge.0",
46 | "babel-eslint": "^8.2.3",
47 | "babel-jest": "^23.6.0",
48 | "babel-loader": "^8.0.0",
49 | "css-loader": "^0.28.11",
50 | "enzyme": "^3.7.0",
51 | "enzyme-adapter-react-16": "^1.6.0",
52 | "enzyme-to-json": "^3.3.4",
53 | "eslint": "^4.9.0",
54 | "eslint-config-airbnb": "16.1.0",
55 | "eslint-config-prettier": "^2.9.0",
56 | "eslint-plugin-import": "^2.7.0",
57 | "eslint-plugin-jsx-a11y": "^6.0.2",
58 | "eslint-plugin-prettier": "^2.6.2",
59 | "eslint-plugin-react": "^7.4.0",
60 | "file-loader": "^1.1.11",
61 | "gh-pages": "^2.0.1",
62 | "html-webpack-plugin": "^3.2.0",
63 | "jest": "^23.6.0",
64 | "mini-css-extract-plugin": "^0.4.0",
65 | "prettier": "^1.14.0",
66 | "react-test-renderer": "^16.5.2",
67 | "webpack": "^4.11.1",
68 | "webpack-cli": "^3.0.2",
69 | "webpack-dev-server": "^3.1.4"
70 | },
71 | "dependencies": {
72 | "@babel/runtime": "^7.1.2",
73 | "react": "16.7.0-alpha.2",
74 | "react-dom": "^16.7.0-alpha.2",
75 | "three": "^0.98.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/VisWithHooks.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react'
2 | import * as THREE from 'three'
3 |
4 | const VisWithHooks = () => {
5 | const mount = useRef(null)
6 | const [isAnimating, setAnimating] = useState(true)
7 | const controls = useRef(null)
8 |
9 | useEffect(() => {
10 | let width = mount.current.clientWidth
11 | let height = mount.current.clientHeight
12 | let frameId
13 |
14 | const scene = new THREE.Scene()
15 | const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
16 | const renderer = new THREE.WebGLRenderer({ antialias: true })
17 | const geometry = new THREE.BoxGeometry(1, 1, 1)
18 | const material = new THREE.MeshBasicMaterial({ color: 0xff00ff })
19 | const cube = new THREE.Mesh(geometry, material)
20 |
21 | camera.position.z = 4
22 | scene.add(cube)
23 | renderer.setClearColor('#000000')
24 | renderer.setSize(width, height)
25 |
26 | const renderScene = () => {
27 | renderer.render(scene, camera)
28 | }
29 |
30 | const handleResize = () => {
31 | width = mount.current.clientWidth
32 | height = mount.current.clientHeight
33 | renderer.setSize(width, height)
34 | camera.aspect = width / height
35 | camera.updateProjectionMatrix()
36 | renderScene()
37 | }
38 |
39 | const animate = () => {
40 | cube.rotation.x += 0.01
41 | cube.rotation.y += 0.01
42 |
43 | renderScene()
44 | frameId = window.requestAnimationFrame(animate)
45 | }
46 |
47 | const start = () => {
48 | if (!frameId) {
49 | frameId = requestAnimationFrame(animate)
50 | }
51 | }
52 |
53 | const stop = () => {
54 | cancelAnimationFrame(frameId)
55 | frameId = null
56 | }
57 |
58 | mount.current.appendChild(renderer.domElement)
59 | window.addEventListener('resize', handleResize)
60 | start()
61 |
62 | controls.current = { start, stop }
63 |
64 | return () => {
65 | stop()
66 | window.removeEventListener('resize', handleResize)
67 | mount.current.removeChild(renderer.domElement)
68 |
69 | scene.remove(cube)
70 | geometry.dispose()
71 | material.dispose()
72 | }
73 | }, [])
74 |
75 | useEffect(
76 | () => {
77 | if (isAnimating) {
78 | controls.current.start()
79 | } else {
80 | controls.current.stop()
81 | }
82 | },
83 | [isAnimating],
84 | )
85 |
86 | /* eslint-disable
87 | jsx-a11y/click-events-have-key-events,
88 | jsx-a11y/no-static-element-interactions
89 | */
90 | return (
91 | setAnimating(!isAnimating)}
95 | />
96 | )
97 | }
98 |
99 | export default VisWithHooks
100 |
--------------------------------------------------------------------------------