├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── dist ├── apple-touch-icon.png ├── favicon.ico ├── fonts │ └── test.png └── images │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── code.txt │ ├── favicon-128.png │ ├── favicon-16x16.png │ ├── favicon-196x196.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ ├── screenshot.png │ └── test.png ├── karma.conf.js ├── package.json ├── readme.md ├── src ├── apple-touch-icon.png ├── components │ ├── App │ │ ├── App.jsx │ │ └── styles.scss │ └── Three │ │ ├── Three.jsx │ │ └── styles.scss ├── favicon.ico ├── fonts │ └── test.png ├── images │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── code.txt │ ├── favicon-128.png │ ├── favicon-16x16.png │ ├── favicon-196x196.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ ├── screenshot.png │ └── test.png ├── index.html ├── index.jsx ├── js │ ├── helpers.js │ ├── sceneManager.js │ ├── three.animation.js │ ├── three.helpers.js │ └── three.loader.js └── scss │ ├── config │ ├── breakpoints.scss │ ├── config.scss │ ├── fonts.scss │ ├── mixins.scss │ ├── normal.scss │ ├── readme.md │ └── variables.scss │ └── main.scss ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", {"modules": false}], 4 | "es2016", 5 | "stage-2", 6 | "react" 7 | ], 8 | "plugins": [ 9 | "react-hot-loader/babel" 10 | ], 11 | "env": { 12 | "test": { 13 | "plugins": [ 14 | ["__coverage__", {"ignore": "*.test.*"}] 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,jsx,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | dist 4 | karma.conf.js 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | parser: "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | // these are only here because I did not 6 | // want to update the entire codebase ¯\_(ツ)_/¯ 7 | "func-names": 0, 8 | "no-var": 0, 9 | "no-unused-vars": 0, 10 | "func-style": 0, 11 | "comma-dangle": 0, 12 | "valid-jsdoc": 0, 13 | "vars-on-top": 0, 14 | "complexity": [2, 6], 15 | "prefer-template": 0, 16 | "no-tabs": 0, // put in by me, need to remove 17 | "indent": 0, 18 | "arrow-body-style": 0, 19 | "padded-blocks" : 0, 20 | "no-param-reassign": 0, 21 | "no-undef": 0, 22 | "no-use-before-define": 0, 23 | }, 24 | "globals": { 25 | "jasmine": false, 26 | "describe": false, 27 | "it": false, 28 | "beforeEach": false, 29 | "expect": false, 30 | "window": false, 31 | "requestAnimationFrame": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .opt-in 3 | coverage/ 4 | !dist/.gitkeep 5 | npm-debug.log 6 | videos 7 | -------------------------------------------------------------------------------- /dist/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/apple-touch-icon.png -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/favicon.ico -------------------------------------------------------------------------------- /dist/fonts/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/fonts/test.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /dist/images/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /dist/images/code.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dist/images/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon-128.png -------------------------------------------------------------------------------- /dist/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon-16x16.png -------------------------------------------------------------------------------- /dist/images/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon-196x196.png -------------------------------------------------------------------------------- /dist/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon-32x32.png -------------------------------------------------------------------------------- /dist/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon-96x96.png -------------------------------------------------------------------------------- /dist/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/favicon.ico -------------------------------------------------------------------------------- /dist/images/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/mstile-144x144.png -------------------------------------------------------------------------------- /dist/images/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/mstile-150x150.png -------------------------------------------------------------------------------- /dist/images/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/mstile-310x150.png -------------------------------------------------------------------------------- /dist/images/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/mstile-310x310.png -------------------------------------------------------------------------------- /dist/images/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/mstile-70x70.png -------------------------------------------------------------------------------- /dist/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/screenshot.png -------------------------------------------------------------------------------- /dist/images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/dist/images/test.png -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.config')({test: true}) 2 | process.env.BABEL_ENV = 'test' // so we load the correct babel plugins 3 | require('babel-register') 4 | 5 | module.exports = function setKarmaConfig(config) { 6 | config.set({ 7 | basePath: '', 8 | frameworks: ['jasmine'], 9 | files: ['src/**/*.test.js'], 10 | exclude: [], 11 | preprocessors: { 12 | 'src/**/*.test.js': ['webpack'], 13 | }, 14 | webpack: webpackConfig, 15 | webpackMiddleware: {noInfo: true}, 16 | reporters: ['progress', 'coverage'], 17 | coverageReporter: { 18 | reporters: [ 19 | {type: 'lcov', dir: 'coverage/', subdir: '.'}, 20 | {type: 'json', dir: 'coverage/', subdir: '.'}, 21 | {type: 'text-summary'}, 22 | ], 23 | }, 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | browsers: ['Firefox'], 28 | singleRun: true, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "@tweenjs/tween.js": "^16.7.0", 5 | "babel-polyfill": "^6.20.0", 6 | "core-decorators": "^0.19.0", 7 | "eslint": "^3.11.1", 8 | "html-webpack-plugin": "^2.24.1", 9 | "lodash": "^4.17.2", 10 | "prop-types": "^15.5.10", 11 | "react": "^15.4.1", 12 | "react-addons-css-transition-group": "^15.5.2", 13 | "react-dom": "^15.4.1", 14 | "react-router-dom": "^4.1.1", 15 | "react-transition-group": "^1.1.3", 16 | "three": "^0.86.0", 17 | "webpack": "^2.6.1", 18 | "webpack-dev-server": "^2.4.5" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^7.1.1", 22 | "babel": "^6.5.2", 23 | "babel-core": "^6.21.0", 24 | "babel-eslint": "6.1.2", 25 | "babel-loader": "^6.2.10", 26 | "babel-plugin-__coverage__": "11.0.0", 27 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 28 | "babel-preset-es2015": "^6.18.0", 29 | "babel-preset-es2016": "^6.16.0", 30 | "babel-preset-react": "^6.16.0", 31 | "babel-preset-stage-2": "6.13.0", 32 | "bourbon-neat": "^2.0.0", 33 | "cpy-cli": "1.0.1", 34 | "css-loader": "0.23.1", 35 | "eslint-config-airbnb": "^13.0.0", 36 | "eslint-loader": "^1.6.1", 37 | "eslint-plugin-import": "^2.2.0", 38 | "eslint-plugin-jsx-a11y": "^2.2.3", 39 | "eslint-plugin-react": "^6.7.1", 40 | "exports-loader": "^0.6.3", 41 | "extract-text-webpack-plugin": "^2.0.0-beta", 42 | "file-loader": "^0.11.1", 43 | "ghooks": "1.3.2", 44 | "image-loader": "^0.0.1", 45 | "image-webpack-loader": "^3.3.1", 46 | "imports-loader": "^0.6.5", 47 | "jasmine-core": "2.4.1", 48 | "karma": "1.2.0", 49 | "karma-chrome-launcher": "2.0.0", 50 | "karma-coverage": "1.1.1", 51 | "karma-firefox-launcher": "1.0.0", 52 | "karma-jasmine": "1.0.2", 53 | "karma-webpack": "1.8.0", 54 | "node-bourbon": "^4.2.8", 55 | "node-sass": "^3.13.0", 56 | "npm-run-all": "3.0.0", 57 | "opt-cli": "1.5.1", 58 | "postcss-loader": "^2.0.5", 59 | "precss": "^1.4.0", 60 | "react-hot-loader": "next", 61 | "rimraf": "2.5.4", 62 | "sass-loader": "^4.0.2", 63 | "shipit": "^1.0.2", 64 | "shipit-deploy": "^2.4.0", 65 | "shipit-npm": "^0.2.0", 66 | "style-loader": "0.13.1", 67 | "webpack-hot-middleware": "^2.15.0", 68 | "webpack-validator": "2.2.7" 69 | }, 70 | "config": { 71 | "ghooks": { 72 | "pre-commit": "opt --in pre-commit --exec \"npm run validate\"" 73 | } 74 | }, 75 | "scripts": { 76 | "test": "./node_modules/karma/bin/karma start karma.conf.js --single-run", 77 | "watch:test": "karma start --auto-watch --no-single-run", 78 | "validate": "npm-run-all --parallel validate-webpack:* lint test build", 79 | "validate-webpack:dev": "webpack-validator webpack.config.js --env.dev", 80 | "validate-webpack:prod": "webpack-validator webpack.config.js --env.prod", 81 | "clean-dist": "rimraf dist", 82 | "copy-files": "cpy 'src/favicon.ico' 'src/apple-touch-icon.png' dist && cpy 'src/images/**/*' dist/images && cpy src/fonts/**/* dist/fonts", 83 | "clean-and-copy": "npm run clean-dist && npm run copy-files", 84 | "prestart": "npm run clean-and-copy", 85 | "start": "webpack-dev-server --hot --inline --env.dev --content-base dist", 86 | "prebuild": "npm run clean-and-copy", 87 | "prebuild:prod": "npm run clean-and-copy", 88 | "build": "webpack --env.dev", 89 | "build:prod": "webpack --env.prod -p", 90 | "lint": "eslint ." 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React + Three.js Boilerplate 2 | 3 | Dive straight into building Three.js projects using ES6 and React for overlaying UI. 4 | 5 | > NOTE: This boilerplate isn't trying to build a wrapper around Three.js. The aim is too allow anyone to quickly get into making three.js projects using ES6 and React. 6 | 7 | ## Technologies 8 | 9 | - React 10 | - Three.js 11 | - TWEEN 12 | - ES6, Webpack, SCSS, Airbnb lint, Karma 13 | 14 | ## Quick Overview 15 | 16 | ```sh 17 | git clone https://github.com/lachlantweedie/react-threejs-es6-boilerplate.git my-new-project 18 | 19 | yarn install 20 | npm start 21 | ``` 22 | 23 | Then open [http://localhost:8080/](http://localhost:8080/) to see your app.
24 | When you’re ready to deploy to production, create a minified bundle with `npm run build:prod`. 25 | 26 | ### Get Started Immediately 27 | 28 | You **don’t** need to install or configure tools like Webpack or Babel.
29 | They are preconfigured and hidden so that you can focus on the code. 30 | 31 | Just clone the project, and you’re good to go. 32 | 33 | ## User Guide 34 | 35 | - Philosophy 36 | - SceneManager 37 | - Creating a scene 38 | - Adding an object 39 | - Update loop 40 | - Removing an object 41 | - Adding/removing custom update methods 42 | - Animation 43 | - Vectors (position, rotation) 44 | - Opacity 45 | - Helpers 46 | 47 | ## Philosophy 48 | 49 | Since most Three.js examples you come across will be in es2015 it's easier to translate those examples to ES6 rather than React components. 50 | 51 | Think of the Three.jsx as your main three app script that contains initialisation and event listeners. We want to make this as simple as possible to let others understand what's happening in your app. That's why we extract all the more complicated functions into their own files. 52 | 53 | 54 | ## SceneManager 55 | 56 | SceneManager aims to simplify the amount of code you need to initialise a three scene in your component code. 57 | 58 | ### Creating a scene 59 | 60 | Creating a three scene as simple as two lines of code. 61 | 62 | ```javascript 63 | const sceneManager = new SceneManager(); 64 | sceneManager.create(); 65 | ``` 66 | 67 | This initialises the three.js scene with default variables and starts the render loop.
68 | This if the function to look for if you'd like to change things like camera type or renderer values. 69 | 70 | ### Adding an object 71 | 72 | ```javascript 73 | const cube = new Mesh( 74 | new BoxGeometry(1, 1, 1), 75 | new MeshBasicMaterial({ 76 | wireframe: true, 77 | color: 0x000000 * Math.random() 78 | }) 79 | ); 80 | 81 | sceneManager.add(cube); 82 | ``` 83 | 84 | ### Update loop 85 | 86 | Every time an object is added to the render loop via the sceneManager, sceneManager checks whether to add and update function into the update loop. 87 | 88 | This makes sure all update methods are managed in one place and are executed within the render loop for efficiency. 89 | 90 | ```javascript 91 | // generic rotate function that could be applied to any object 92 | function rotate(delta) { 93 | this.rotation.x += 0.1 * delta; 94 | this.rotation.y += 0.1 * delta; 95 | this.rotation.z += 0.1 * delta; 96 | } 97 | 98 | // bind this to cube 99 | cube.update = rotate.bind(cube); 100 | 101 | ``` 102 | 103 | ### Removing an object 104 | 105 | Removing an object also checks to remove the update method from the update loop. 106 | 107 | ```javascript 108 | sceneManager.remove(cube); 109 | ``` 110 | 111 | ### Adding/removing custom update methods 112 | 113 | #### Adding 114 | 115 | ```javascript 116 | sceneManager.addUpdate((delta) => { 117 | cube.position.x += 0.1 * delta; 118 | }, 'customMoveCube'); 119 | ``` 120 | 121 | #### Removing 122 | 123 | ```javascript 124 | sceneManager.removeUpdate('customMoveCube'); 125 | ``` 126 | 127 | ## Animation 128 | 129 | The TWEEN animation library is used in this boilerplate because it animates within the render loop and is the best for efficiency. 130 | 131 | ### Vectors (position, rotation) 132 | 133 | ```javascript 134 | import { animateVector3 } from '../../js/three.animation'; 135 | 136 | ... 137 | 138 | animateVector3(cube.position, new Vector3(0, 10, 0), { 139 | duration: 5000, 140 | easing: Easing.Linear.None; 141 | yoyo: true, 142 | repeat: Infinity, 143 | delay: 0 144 | }); 145 | ``` 146 | 147 | > Remember to import { Easing } from '@tweenjs/tween.js' when modifying easing values. 148 | 149 | ### Opacity 150 | 151 | The fade function from three.helpers fades 'in' or 'out' from 0 to 1. 152 | 153 | ```javascript 154 | fade(cube, 'in', { duration: 5000 }); 155 | ``` 156 | 157 | ### Helpers 158 | 159 | All helper files are located in `src/js/`. 160 | 161 | Complicated or repetitive logic should be extracted into these files to keep logic within the `Three.jsx` component straightforward and readable. 162 | -------------------------------------------------------------------------------- /src/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/apple-touch-icon.png -------------------------------------------------------------------------------- /src/components/App/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import css from './styles.scss'; 3 | import Three from '../Three/Three'; 4 | import { $on } from '../../js/helpers'; 5 | 6 | class App extends Component { 7 | 8 | constructor() { 9 | super(); 10 | 11 | this.state = { loaded: false }; 12 | 13 | $on(window, 'load', () => { 14 | this.setState({ loaded: true }); 15 | }); 16 | 17 | } 18 | 19 | render() { 20 | return ( 21 |
22 | 23 |
24 |

25 | 26 | React + Three.js Boilerplate; 27 | 28 |

29 |
30 |
31 | ); 32 | } 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /src/components/App/styles.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-weight: bold; 3 | } 4 | 5 | .h1 { 6 | @include clearfix; 7 | font-size: 32px; 8 | 9 | @include mobile { 10 | font-size: 22px; 11 | } 12 | } 13 | 14 | main { 15 | padding-top: 60px; 16 | padding-left: 80px; 17 | 18 | @include tablet { 19 | padding: 40px 70px; 20 | } 21 | 22 | @include mobile { 23 | padding: 20px 20px; 24 | } 25 | } 26 | 27 | $t: opacity 700ms ease, left 1000ms ease; 28 | $delay: 600ms; 29 | 30 | // animate 31 | h1 { 32 | position: relative; 33 | opacity: 0; 34 | transition: $t; 35 | left: -20px; 36 | } 37 | 38 | .loaded { 39 | h1 { 40 | opacity: 1; 41 | left: 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Three/Three.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Vector3, BasicMeshMaterial, Raycaster } from 'three'; 3 | 4 | import { $on } from '../../js/helpers'; 5 | import SceneManager from '../../js/sceneManager'; 6 | import { animateVector3, fade } from '../../js/three.animation'; 7 | import { loadModel, getModel } from '../../js/three.loader'; 8 | import { hLight, dLight, makeCube, makeSphere } from '../../js/three.helpers'; 9 | 10 | import css from './styles.scss'; 11 | 12 | class Three extends Component { 13 | 14 | componentDidMount() { 15 | 16 | $on(window, 'load', () => { 17 | 18 | const sceneManager = new SceneManager(); 19 | 20 | this.sceneManager = sceneManager; 21 | 22 | sceneManager.create(); 23 | 24 | sceneManager.add(hLight()); 25 | sceneManager.add(dLight()); 26 | 27 | const cube = makeCube(); 28 | const sphere = makeSphere(); 29 | 30 | cube.position.x = -10; 31 | sphere.position.set(25, 2, -3); 32 | 33 | cube.update = rotate.bind(cube); 34 | sphere.update = rotate.bind(sphere); 35 | 36 | function rotate(delta) { 37 | this.rotation.x += 0.1 * delta; 38 | this.rotation.y += 0.1 * delta; 39 | this.rotation.z += 0.1 * delta; 40 | } 41 | 42 | sceneManager.add(cube); 43 | sceneManager.add(sphere); 44 | 45 | animate(cube, 10, 25000); 46 | animate(sphere, -25, 40000); 47 | 48 | function animate(obj, x, time) { 49 | animateVector3(obj.position, new Vector3(x, obj.position.y, obj.position.z), { 50 | duration: time, yoyo: true, repeat: Infinity 51 | }); 52 | } 53 | 54 | // fade example 55 | let way = 'in'; 56 | setInterval(() => { 57 | fade(cube, way, { duration: 5000 }); 58 | fade(sphere, way, { duration: 5000 }); 59 | way = way === 'in' 60 | ? 'out' 61 | : 'in'; 62 | }, 10000); 63 | 64 | }); 65 | 66 | } 67 | 68 | render() { 69 | return
; 70 | } 71 | } 72 | 73 | export default Three; 74 | -------------------------------------------------------------------------------- /src/components/Three/styles.scss: -------------------------------------------------------------------------------- 1 | .three-container { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100vh; 7 | z-index: -1; 8 | } 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/favicon.ico -------------------------------------------------------------------------------- /src/fonts/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/fonts/test.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/images/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/images/code.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/images/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon-128.png -------------------------------------------------------------------------------- /src/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon-16x16.png -------------------------------------------------------------------------------- /src/images/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon-196x196.png -------------------------------------------------------------------------------- /src/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon-32x32.png -------------------------------------------------------------------------------- /src/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon-96x96.png -------------------------------------------------------------------------------- /src/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/favicon.ico -------------------------------------------------------------------------------- /src/images/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/mstile-144x144.png -------------------------------------------------------------------------------- /src/images/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/mstile-150x150.png -------------------------------------------------------------------------------- /src/images/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/mstile-310x150.png -------------------------------------------------------------------------------- /src/images/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/mstile-310x310.png -------------------------------------------------------------------------------- /src/images/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/mstile-70x70.png -------------------------------------------------------------------------------- /src/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/screenshot.png -------------------------------------------------------------------------------- /src/images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/images/test.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | React Three.js Boilerplate 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | import App from './components/App/App'; 6 | import css from './scss/main.scss'; 7 | 8 | if (module.hot) { 9 | module.hot.accept(); 10 | } 11 | 12 | ReactDOM.render( 13 | , 14 | document.getElementById('app') 15 | ); 16 | -------------------------------------------------------------------------------- /src/js/helpers.js: -------------------------------------------------------------------------------- 1 | export { 2 | qs, 3 | qsa, 4 | removeClass, 5 | $on, 6 | $delegate, 7 | $parent 8 | }; 9 | 10 | // Get element(s) by CSS selector: 11 | function qs(selector, scope) { 12 | return (scope || document).querySelector(selector); 13 | } 14 | function qsa(selector, scope) { 15 | return (scope || document).querySelectorAll(selector); 16 | } 17 | function removeClass(el, className) { 18 | if (el.length) { 19 | for (var i = 0; i < el.length; i += 1) { 20 | removeClass(el[i], className); 21 | } 22 | return; 23 | } 24 | if (el.classList) { 25 | el.classList.remove(className); 26 | } else { 27 | el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 28 | } 29 | } 30 | 31 | // addEventListener wrapper: 32 | function $on(target, type, callback, useCapture) { 33 | target.addEventListener(type, callback, !!useCapture); 34 | } 35 | 36 | // Attach a handler to event for all elements that match the selector, 37 | // now or in the future, based on a root element 38 | function $delegate(target, selector, type, handler) { 39 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur 40 | var useCapture = type === 'blur' || type === 'focus'; 41 | $on(target, type, dispatchEvent, useCapture); 42 | 43 | function dispatchEvent(event) { 44 | var targetElement = event.target; 45 | var potentialElements = qsa(selector, target); 46 | var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; 47 | 48 | if (hasMatch) { 49 | handler.call(targetElement, event); 50 | } 51 | } 52 | } 53 | 54 | // Find the element's parent with the given tag name: 55 | // $parent(qs('a'), 'div'); 56 | function $parent(element, tagName) { 57 | if (!element.parentNode) { 58 | return undefined; 59 | } 60 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { 61 | return element.parentNode; 62 | } 63 | return $parent(element.parentNode, tagName); 64 | } 65 | 66 | // Allow for looping on nodes by chaining: 67 | // qsa('.foo').forEach(function () {}) 68 | NodeList.prototype.forEach = Array.prototype.forEach; 69 | -------------------------------------------------------------------------------- /src/js/sceneManager.js: -------------------------------------------------------------------------------- 1 | import { Scene, PerspectiveCamera, WebGLRenderer, Clock } from 'three'; 2 | import TWEEN from '@tweenjs/tween.js'; 3 | import { qs, $on } from './helpers'; 4 | import { trackOriginalOpacities } from './three.animation'; 5 | 6 | 7 | /** 8 | * Creates a new SceneManager instance 9 | * 10 | * @constructor 11 | */ 12 | export default function SceneManager(selector = '#ThreeJS') { 13 | this.container = qs(selector); 14 | this.scene = new Scene(); 15 | this.updates = []; 16 | } 17 | 18 | /** 19 | * Creates a new THREE scene 20 | * 21 | * @param {function} [callback] The callback to fire after the model is created 22 | */ 23 | SceneManager.prototype.create = function (callback = () => {}) { 24 | 25 | const SCREEN_WIDTH = this.container.offsetWidth; 26 | const SCREEN_HEIGHT = this.container.offsetHeight; 27 | const VIEW_ANGLE = 45; 28 | const ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT; 29 | const NEAR = 0.1; 30 | const FAR = 10000; 31 | 32 | this.camera = new PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR); 33 | this.camera.updateProjectionMatrix(); 34 | this.camera.position.set(0, 0, 10); 35 | this.scene.add(this.camera); 36 | 37 | this.clock = new Clock(); 38 | 39 | this.renderer = new WebGLRenderer({ alpha: true, antialias: window.devicePixelRatio }); 40 | 41 | this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); 42 | this.renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1); 43 | this.renderer.sortObjects = true; 44 | this.container.appendChild(this.renderer.domElement); 45 | 46 | $on(window, 'resize', this.onresize); 47 | 48 | this.render(); 49 | 50 | callback(); 51 | 52 | }; 53 | 54 | SceneManager.prototype.render = function () { 55 | 56 | requestAnimationFrame(this.render.bind(this)); 57 | 58 | const delta = this.clock.getDelta(); 59 | 60 | // individual object updates 61 | for (var i = 0; i < this.updates.length; i += 1) { 62 | this.updates[i].fn(delta); 63 | } 64 | 65 | TWEEN.update(); 66 | 67 | this.renderer.render(this.scene, this.camera); 68 | 69 | }; 70 | 71 | SceneManager.prototype.onresize = function () { 72 | 73 | const SCREEN_WIDTH = this.container.offsetWidth; 74 | const SCREEN_HEIGHT = this.container.offsetHeight; 75 | 76 | this.camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT; 77 | 78 | this.camera.updateProjectionMatrix(); 79 | 80 | this.renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); 81 | 82 | }; 83 | 84 | SceneManager.prototype.add = function (obj) { 85 | 86 | if (obj.update) { 87 | this.addUpdate(obj.update, obj.uuid); 88 | } 89 | 90 | if (obj.constructor.name === 'Mesh') { 91 | trackOriginalOpacities(obj); 92 | } 93 | 94 | this.scene.add(obj); 95 | 96 | }; 97 | 98 | SceneManager.prototype.remove = function (obj) { 99 | 100 | this.removeUpdate(obj.uuid); 101 | this.scene.remove(obj); 102 | 103 | }; 104 | 105 | SceneManager.prototype.addUpdate = function (fn, id) { 106 | 107 | this.updates.push({ fn, id }); 108 | 109 | }; 110 | 111 | SceneManager.prototype.removeUpdate = function (id) { 112 | 113 | for (var i = 0; i < this.updates.length; i += 1) { 114 | if (this.updates[i].id === id) { 115 | this.updates.splice(i, 1); // remove it 116 | break; 117 | } 118 | } 119 | 120 | }; 121 | -------------------------------------------------------------------------------- /src/js/three.animation.js: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three'; 2 | import { Tween, Easing } from '@tweenjs/tween.js'; 3 | 4 | /** 5 | * Animates a Vector3 to the target Vector3 6 | * 7 | * @param {[Vector3]} vectorToAnimate [The vector3 to apply the animation to] 8 | * @param {[Vector3]} target [Target Vector3] 9 | * @param {[Object]} options [The animation options for Tween.js] 10 | */ 11 | export function animateVector3(vectorToAnimate, target, options = {}) { 12 | options = setOptionDefaults(options); 13 | tween(vectorToAnimate, { x: target.x, y: target.y, z: target.z }, options); 14 | } 15 | 16 | /** 17 | * Fades an object in or out given the direction 18 | * 19 | * @param {[Mesh]} mesh [The mesh to fade] 20 | * @param {[type]} direction [Whether to fade 'in' or 'out'] 21 | * @param {[type]} options [The animation options for Tween.js] 22 | */ 23 | export function fade(mesh, direction = 'in', options = {}) { 24 | 25 | options = setOptionDefaults(options); 26 | 27 | const current = { percentage: direction === 'in' ? 1 : 0 }; 28 | const mats = mesh.material.materials ? mesh.material.materials : [mesh.material]; 29 | const originals = mesh.userData.originalOpacities ? mesh.userData.originalOpacities : [1]; 30 | const { update } = options; 31 | 32 | options.update = () => { 33 | for (var i = 0; i < mats.length; i += 1) { 34 | mats[i].opacity = originals[i] * current.percentage; 35 | } 36 | if (update) update(); 37 | }; 38 | 39 | tween(current, { percentage: direction === 'in' ? 0 : 1 }, options); 40 | 41 | } 42 | 43 | /** 44 | * Adds the original opacities of the materials to the object 45 | * Good idea to put this where you add objects to the scene with the check below. 46 | * 47 | * if (obj.constructor.name === 'Mesh') { 48 | * trackOriginalOpacities(obj); 49 | * } 50 | * 51 | * @param {[type]} mesh [description] 52 | */ 53 | export function trackOriginalOpacities(mesh) { 54 | 55 | const opacities = []; 56 | const materials = mesh.material.materials ? mesh.material.materials : [mesh.material]; 57 | 58 | for (var i = 0; i < materials.length; i += 1) { 59 | materials[i].transparent = true; 60 | opacities.push(materials[i].opacity); 61 | } 62 | 63 | mesh.userData.originalOpacities = opacities; 64 | 65 | } 66 | 67 | 68 | /** 69 | * Generic tween used for all tweens in Three.js 70 | * 71 | * @param {[Object]} current [The object to apply the animation to] 72 | * @param {[type]} targets [The target object for current] 73 | * @param {[type]} options [Tween options] 74 | */ 75 | function tween(current, targets, options) { 76 | const tweenVector3 = new Tween(current) 77 | .to(targets, options.duration) 78 | .easing(options.easing) 79 | .yoyo(options.yoyo) 80 | .repeat(options.repeat) 81 | .delay(options.delay) 82 | .onUpdate((d) => { 83 | if (options.update) options.update(d); 84 | }) 85 | .onComplete(() => { 86 | if (options.callback) options.callback(); 87 | }); 88 | 89 | tweenVector3.start(); 90 | } 91 | 92 | /** 93 | * Sets the default options for the above tweens 94 | * 95 | * @param {[Object]} options - list of options 96 | */ 97 | function setOptionDefaults(options) { 98 | options.duration = options.duration || 2000; 99 | options.easing = options.easing || Easing.Linear.None; 100 | options.yoyo = options.yoyo || false; 101 | options.repeat = options.repeat || 0; 102 | options.delay = options.delay || 0; 103 | return options; 104 | } 105 | -------------------------------------------------------------------------------- /src/js/three.helpers.js: -------------------------------------------------------------------------------- 1 | import { 2 | HemisphereLight, 3 | DirectionalLight, 4 | BoxGeometry, 5 | MeshBasicMaterial, 6 | Mesh, 7 | SphereGeometry 8 | } from 'three'; 9 | 10 | // THREE hemisphere light 11 | export function hLight() { 12 | const hemiLight = new HemisphereLight(0xffffff, 0xffffff, 0.9); 13 | hemiLight.position.set(0, 500, 0); 14 | return hemiLight; 15 | } 16 | 17 | export function dLight() { 18 | const dirLight = new DirectionalLight(0xffffff, 1); 19 | dirLight.position.set(-1, 0.75, 1); 20 | dirLight.position.multiplyScalar(50); 21 | return dirLight; 22 | } 23 | 24 | export function makeCube() { 25 | return new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial({ 26 | wireframe: true, 27 | color: 0x000000 * Math.random() 28 | })); 29 | } 30 | 31 | export function makeSphere() { 32 | return new Mesh(new SphereGeometry(9, 14, 12, 0, Math.PI * 2, 0, Math.PI * 2), 33 | new MeshBasicMaterial({ 34 | wireframe: true, 35 | color: 0x000000 * Math.random() 36 | })); 37 | } 38 | -------------------------------------------------------------------------------- /src/js/three.loader.js: -------------------------------------------------------------------------------- 1 | import { JSONLoader, MultiMaterial, Mesh, LoadingManager } from 'three'; 2 | 3 | const cache = []; 4 | const loader = new JSONLoader(); 5 | 6 | export function loadModel(path, name, callback) { 7 | 8 | loader.load(path, (geometry, materials) => { 9 | const model = new Mesh(geometry, new MultiMaterial(materials)); 10 | model.name = name; 11 | cache.push({ name, model }); 12 | if (callback) callback(); 13 | }); 14 | 15 | } 16 | 17 | export function getModel(name) { 18 | 19 | for (var i = 0; i < cache.length; i += 1) { 20 | if (cache[i].name === name) { 21 | return cache[i].model; 22 | } 23 | } 24 | 25 | return undefined; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/scss/config/breakpoints.scss: -------------------------------------------------------------------------------- 1 | 2 | $minWidth: 320px; 3 | $mobile_mid: 480px; 4 | $mobile: 640px; 5 | $tablet: 768px; 6 | $desktop: 960px; 7 | $wide: 1025px; 8 | $widest: 1200px; 9 | 10 | @mixin mediaGreaterThan($breakpoint) { 11 | @media only screen and (min-width: $breakpoint + 1) { 12 | @content; 13 | } 14 | } 15 | 16 | @mixin mediaLessThan($breakpoint) { 17 | @media only screen and (max-width: ($breakpoint)) { 18 | @content; 19 | } 20 | } 21 | 22 | @mixin tablet { 23 | @include mediaLessThan($tablet) { 24 | @content; 25 | } 26 | } 27 | 28 | @mixin mobile { 29 | @include mediaLessThan($mobile) { 30 | @content; 31 | } 32 | } 33 | 34 | .desktop { 35 | @include mediaLessThan($tablet) { 36 | display: none; 37 | } 38 | } 39 | 40 | .mobile { 41 | @include mediaGreaterThan($tablet) { 42 | display: none; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/scss/config/config.scss: -------------------------------------------------------------------------------- 1 | @import 'bourbon'; 2 | @import 'variables'; 3 | @import 'mixins'; 4 | @import 'breakpoints'; 5 | -------------------------------------------------------------------------------- /src/scss/config/fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/scss/config/fonts.scss -------------------------------------------------------------------------------- /src/scss/config/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin center() { 2 | @include position(absolute, 50% null null 50%); 3 | transform: translateX(-50%) translateY(-50%); 4 | } 5 | -------------------------------------------------------------------------------- /src/scss/config/normal.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v6.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in 9 | * IE on Windows Phone and in iOS. 10 | */ 11 | 12 | html { 13 | line-height: 1.15; /* 1 */ 14 | -ms-text-size-adjust: 100%; /* 2 */ 15 | -webkit-text-size-adjust: 100%; /* 2 */ 16 | } 17 | 18 | /* Sections 19 | ========================================================================== */ 20 | 21 | /** 22 | * Add the correct display in IE 9-. 23 | */ 24 | 25 | article, 26 | aside, 27 | footer, 28 | header, 29 | nav, 30 | section { 31 | display: block; 32 | } 33 | 34 | /** 35 | * Correct the font size and margin on `h1` elements within `section` and 36 | * `article` contexts in Chrome, Firefox, and Safari. 37 | */ 38 | 39 | h1 { 40 | font-size: 2em; 41 | margin: 0.67em 0; 42 | } 43 | 44 | /* Grouping content 45 | ========================================================================== */ 46 | 47 | /** 48 | * Add the correct display in IE 9-. 49 | * 1. Add the correct display in IE. 50 | */ 51 | 52 | figcaption, 53 | figure, 54 | main { /* 1 */ 55 | display: block; 56 | } 57 | 58 | /** 59 | * Add the correct margin in IE 8. 60 | */ 61 | 62 | figure { 63 | margin: 1em 40px; 64 | } 65 | 66 | /** 67 | * 1. Add the correct box sizing in Firefox. 68 | * 2. Show the overflow in Edge and IE. 69 | */ 70 | 71 | hr { 72 | box-sizing: content-box; /* 1 */ 73 | height: 0; /* 1 */ 74 | overflow: visible; /* 2 */ 75 | } 76 | 77 | /** 78 | * 1. Correct the inheritance and scaling of font size in all browsers. 79 | * 2. Correct the odd `em` font sizing in all browsers. 80 | */ 81 | 82 | pre { 83 | font-family: monospace, monospace; /* 1 */ 84 | font-size: 1em; /* 2 */ 85 | } 86 | 87 | /* Text-level semantics 88 | ========================================================================== */ 89 | 90 | /** 91 | * 1. Remove the gray background on active links in IE 10. 92 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 93 | */ 94 | 95 | a { 96 | background-color: transparent; /* 1 */ 97 | -webkit-text-decoration-skip: objects; /* 2 */ 98 | } 99 | 100 | /** 101 | * 1. Remove the bottom border in Chrome 57- and Firefox 39-. 102 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 103 | */ 104 | 105 | abbr[title] { 106 | border-bottom: none; /* 1 */ 107 | text-decoration: underline; /* 2 */ 108 | text-decoration: underline dotted; /* 2 */ 109 | } 110 | 111 | /** 112 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: inherit; 118 | } 119 | 120 | /** 121 | * Add the correct font weight in Chrome, Edge, and Safari. 122 | */ 123 | 124 | b, 125 | strong { 126 | font-weight: bolder; 127 | } 128 | 129 | /** 130 | * 1. Correct the inheritance and scaling of font size in all browsers. 131 | * 2. Correct the odd `em` font sizing in all browsers. 132 | */ 133 | 134 | code, 135 | kbd, 136 | samp { 137 | font-family: monospace, monospace; /* 1 */ 138 | font-size: 1em; /* 2 */ 139 | } 140 | 141 | /** 142 | * Add the correct font style in Android 4.3-. 143 | */ 144 | 145 | dfn { 146 | font-style: italic; 147 | } 148 | 149 | /** 150 | * Add the correct background and color in IE 9-. 151 | */ 152 | 153 | mark { 154 | background-color: #ff0; 155 | color: #000; 156 | } 157 | 158 | /** 159 | * Add the correct font size in all browsers. 160 | */ 161 | 162 | small { 163 | font-size: 80%; 164 | } 165 | 166 | /** 167 | * Prevent `sub` and `sup` elements from affecting the line height in 168 | * all browsers. 169 | */ 170 | 171 | sub, 172 | sup { 173 | font-size: 75%; 174 | line-height: 0; 175 | position: relative; 176 | vertical-align: baseline; 177 | } 178 | 179 | sub { 180 | bottom: -0.25em; 181 | } 182 | 183 | sup { 184 | top: -0.5em; 185 | } 186 | 187 | /* Embedded content 188 | ========================================================================== */ 189 | 190 | /** 191 | * Add the correct display in IE 9-. 192 | */ 193 | 194 | audio, 195 | video { 196 | display: inline-block; 197 | } 198 | 199 | /** 200 | * Add the correct display in iOS 4-7. 201 | */ 202 | 203 | audio:not([controls]) { 204 | display: none; 205 | height: 0; 206 | } 207 | 208 | /** 209 | * Remove the border on images inside links in IE 10-. 210 | */ 211 | 212 | img { 213 | border-style: none; 214 | } 215 | 216 | /** 217 | * Hide the overflow in IE. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* Forms 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove the margin in Firefox and Safari. 229 | */ 230 | 231 | button, 232 | input, 233 | optgroup, 234 | select, 235 | textarea { 236 | margin: 0; 237 | } 238 | 239 | /** 240 | * Show the overflow in IE. 241 | * 1. Show the overflow in Edge. 242 | */ 243 | 244 | button, 245 | input { /* 1 */ 246 | overflow: visible; 247 | } 248 | 249 | /** 250 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 251 | * 1. Remove the inheritance of text transform in Firefox. 252 | */ 253 | 254 | button, 255 | select { /* 1 */ 256 | text-transform: none; 257 | } 258 | 259 | /** 260 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 261 | * controls in Android 4. 262 | * 2. Correct the inability to style clickable types in iOS and Safari. 263 | */ 264 | 265 | button, 266 | html [type="button"], /* 1 */ 267 | [type="reset"], 268 | [type="submit"] { 269 | -webkit-appearance: button; /* 2 */ 270 | } 271 | 272 | /** 273 | * Remove the inner border and padding in Firefox. 274 | */ 275 | 276 | button::-moz-focus-inner, 277 | [type="button"]::-moz-focus-inner, 278 | [type="reset"]::-moz-focus-inner, 279 | [type="submit"]::-moz-focus-inner { 280 | border-style: none; 281 | padding: 0; 282 | } 283 | 284 | /** 285 | * Restore the focus styles unset by the previous rule. 286 | */ 287 | 288 | button:-moz-focusring, 289 | [type="button"]:-moz-focusring, 290 | [type="reset"]:-moz-focusring, 291 | [type="submit"]:-moz-focusring { 292 | outline: 1px dotted ButtonText; 293 | } 294 | 295 | /** 296 | * 1. Correct the text wrapping in Edge and IE. 297 | * 2. Correct the color inheritance from `fieldset` elements in IE. 298 | * 3. Remove the padding so developers are not caught out when they zero out 299 | * `fieldset` elements in all browsers. 300 | */ 301 | 302 | legend { 303 | box-sizing: border-box; /* 1 */ 304 | color: inherit; /* 2 */ 305 | display: table; /* 1 */ 306 | max-width: 100%; /* 1 */ 307 | padding: 0; /* 3 */ 308 | white-space: normal; /* 1 */ 309 | } 310 | 311 | /** 312 | * 1. Add the correct display in IE 9-. 313 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 314 | */ 315 | 316 | progress { 317 | display: inline-block; /* 1 */ 318 | vertical-align: baseline; /* 2 */ 319 | } 320 | 321 | /** 322 | * Remove the default vertical scrollbar in IE. 323 | */ 324 | 325 | textarea { 326 | overflow: auto; 327 | } 328 | 329 | /** 330 | * 1. Add the correct box sizing in IE 10-. 331 | * 2. Remove the padding in IE 10-. 332 | */ 333 | 334 | [type="checkbox"], 335 | [type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Correct the cursor style of increment and decrement buttons in Chrome. 342 | */ 343 | 344 | [type="number"]::-webkit-inner-spin-button, 345 | [type="number"]::-webkit-outer-spin-button { 346 | height: auto; 347 | } 348 | 349 | /** 350 | * 1. Correct the odd appearance in Chrome and Safari. 351 | * 2. Correct the outline style in Safari. 352 | */ 353 | 354 | [type="search"] { 355 | -webkit-appearance: textfield; /* 1 */ 356 | outline-offset: -2px; /* 2 */ 357 | } 358 | 359 | /** 360 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 361 | */ 362 | 363 | [type="search"]::-webkit-search-cancel-button, 364 | [type="search"]::-webkit-search-decoration { 365 | -webkit-appearance: none; 366 | } 367 | 368 | /** 369 | * 1. Correct the inability to style clickable types in iOS and Safari. 370 | * 2. Change font properties to `inherit` in Safari. 371 | */ 372 | 373 | ::-webkit-file-upload-button { 374 | -webkit-appearance: button; /* 1 */ 375 | font: inherit; /* 2 */ 376 | } 377 | 378 | /* Interactive 379 | ========================================================================== */ 380 | 381 | /* 382 | * Add the correct display in IE 9-. 383 | * 1. Add the correct display in Edge, IE, and Firefox. 384 | */ 385 | 386 | details, /* 1 */ 387 | menu { 388 | display: block; 389 | } 390 | 391 | /* 392 | * Add the correct display in all browsers. 393 | */ 394 | 395 | summary { 396 | display: list-item; 397 | } 398 | 399 | /* Scripting 400 | ========================================================================== */ 401 | 402 | /** 403 | * Add the correct display in IE 9-. 404 | */ 405 | 406 | canvas { 407 | display: inline-block; 408 | } 409 | 410 | /** 411 | * Add the correct display in IE. 412 | */ 413 | 414 | template { 415 | display: none; 416 | } 417 | 418 | /* Hidden 419 | ========================================================================== */ 420 | 421 | /** 422 | * Add the correct display in IE 10-. 423 | */ 424 | 425 | [hidden] { 426 | display: none; 427 | } 428 | -------------------------------------------------------------------------------- /src/scss/config/readme.md: -------------------------------------------------------------------------------- 1 | These files are included in every imported CSS file. 2 | Don't include any regular CSS here, only mixins and variables should be used. 3 | -------------------------------------------------------------------------------- /src/scss/config/variables.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachlantweedie/react-threejs-es6-boilerplate/acdfac93e9aa3160b78f2bd676630fe9c41dc25f/src/scss/config/variables.scss -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import './config/normal'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | background: #EFEFEF; 9 | font-family: 'Lato', sans-serif; 10 | color: #3a3a3a; 11 | margin: 0; 12 | min-height: 100vh; 13 | overflow: hidden; 14 | } 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const {resolve} = require('path'); 5 | const bourbon = require('node-bourbon').includePaths; 6 | const neat = require('bourbon-neat').includePaths; 7 | 8 | const sassLoader = { 9 | loader: "sass-loader", 10 | options: { 11 | data: '@import "src/scss/config/config.scss";', 12 | includePaths: [ 13 | bourbon, 14 | neat, 15 | resolve(__dirname, 'node_modules'), 16 | resolve(__dirname, "src/scss/config") 17 | ] 18 | } 19 | }; 20 | 21 | const CSSLoader = { 22 | loader: 'css-loader', 23 | options: { 24 | importLoaders: 1 25 | } 26 | }; 27 | 28 | const postCSSLoader = { 29 | loader: 'postcss-loader', 30 | options: { 31 | includePaths: [resolve(__dirname, '')], 32 | plugins: function() { 33 | return [ 34 | require('precss'), require('autoprefixer')({ 35 | browsers: ['last 2 version', '> 5%', 'ie 9'] 36 | }) 37 | ]; 38 | } 39 | } 40 | }; 41 | 42 | module.exports = (env) => { 43 | return { 44 | entry: { 45 | app: env.prod 46 | ? ['./index.jsx'] 47 | : [ 48 | 'react-hot-loader/patch', 'webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './index.jsx' 49 | ], 50 | vendor: ['react', 'lodash', 'three', '@tweenjs/tween.js'] 51 | }, 52 | output: { 53 | filename: 'bundle.[name].[hash].js', 54 | path: resolve(__dirname, 'dist'), 55 | pathinfo: true 56 | }, 57 | devServer: { 58 | stats: 'errors-only', 59 | historyApiFallback: true, 60 | hot: true, 61 | compress: true, // gzip 62 | open: true // open in browser and run 63 | }, 64 | context: resolve(__dirname, 'src'), 65 | devtool: env.prod 66 | ? 'source-map' 67 | : 'eval', 68 | module: { 69 | rules: [ 70 | { 71 | test: /\.(js|jsx)?$/, 72 | use: [ 73 | 'babel-loader', 'eslint-loader' 74 | ], 75 | exclude: /node_modules/ 76 | }, { 77 | test: /\.scss$/, 78 | use: ExtractTextPlugin.extract({ 79 | fallback: "style-loader", 80 | use: [ 81 | CSSLoader, postCSSLoader, sassLoader 82 | ], 83 | // publicPath: 'dist/', 84 | }) 85 | }, { 86 | test: /\.(jpe?g|png|gif|svg)$/i, 87 | use: [ 88 | { 89 | loader: 'file-loader', 90 | options: { 91 | name: '[name].[hash].[ext]', 92 | outputPath: 'images/', 93 | // publicPath: 'images/', 94 | } 95 | } 96 | ] 97 | } 98 | ] 99 | }, 100 | resolve: { 101 | extensions: [ 102 | '.js', '.jsx' 103 | ], 104 | modules: [ 105 | resolve(__dirname, 'node_modules'), 106 | './src' 107 | ] 108 | }, 109 | plugins: [ 110 | new HtmlWebpackPlugin({template: './index.html'}), 111 | new webpack.optimize.CommonsChunkPlugin({name: 'vendor'}), 112 | new ExtractTextPlugin({filename: 'bundle.[contenthash].css', disable: false, allChunks: true}) 113 | ] 114 | }; 115 | }; 116 | --------------------------------------------------------------------------------