Skip to content

Instantly share code, notes, and snippets.

@jeff-silva
Last active October 19, 2021 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeff-silva/d8b191142b6765dea724f6c6e0b9d108 to your computer and use it in GitHub Desktop.
Save jeff-silva/d8b191142b6765dea724f6c6e0b9d108 to your computer and use it in GitHub Desktop.
ThreeJS
import * as THREE from 'https://unpkg.com/three@0.127.0/build/three.module.js';
import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'https://unpkg.com/three@0.127.0/examples/jsm/renderers/CSS3DRenderer.js';
export default class {
settings = {
rendererType: 'WebGLRenderer',
renderer: {
antialias: true,
alpha: true,
},
};
constructor(attributes={}) {
// Create methods and attributes
let attrs = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
for(let i in attributes) {
if (attrs.indexOf(i)>=0 || typeof this[i]!='undefined') continue;
this[i] = attributes[i];
delete attributes[i];
}
if (!this.el) return;
this.width = this.el.offsetWidth;
this.height = this.el.offsetHeight;
this.clock = new THREE.Clock();
this.events = this.events||[];
this.settings = this.jsonMerge(this.settings, attributes.settings||{});
// Before create
this.runEvents('beforeCreate', {});
// Scene
if (!this.scene) {
this.scene = new THREE.Scene();
}
// Camera
if (!this.camera) {
this.camera = new THREE.PerspectiveCamera(50, this.width/this.height, .1, 1000);
this.camera.position.y = 1;
}
// Grid Helper
if (this.settings.gridHelper) {
this.settings.gridHelper = Object.assign({
size: 10,
divisions: 10,
}, this.settings.gridHelper||{});
this.gridHelper = new THREE.GridHelper(this.settings.gridHelper.size, this.settings.gridHelper.divisions);
this.scene.add(this.gridHelper);
}
// Renderer
this.renderer = this.renderer || (() => {
let renderer;
if (this.settings.rendererType=='CSS3DRenderer') {
renderer = new CSS3DRenderer();
}
else {
renderer = new THREE.WebGLRenderer(this.settings.renderer);
renderer.setPixelRatio(this.width/this.height);
}
this.el.appendChild(renderer.domElement);
renderer.setSize(this.width, this.height);
return renderer;
})();
window.addEventListener('resize', (ev) => {
this.width = this.el.offsetWidth;
this.height = this.el.offsetHeight;
this.camera.aspect = this.width / this.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.width, this.height);
});
// App focused
this.focused = false;
['click'].forEach(evt => {
window.addEventListener(evt, (ev) => {
this.focused = this.el.contains(ev.target);
});
});
// Mouse events
this.mouse = {};
['mousemove', 'click', 'keyup', 'wheel'].forEach(evt => {
this.el.addEventListener(evt, (ev) => {
this.mouse = {
type: ev.type,
x: ev.layerX,
y: ev.layerY,
lastX: (this.mouse.x||0),
lastY: (this.mouse.y||0),
speed: 0,
buttons: ev.buttons,
ctrlKey: ev.ctrlKey,
shiftKey: ev.shiftKey,
altKey: ev.altKey,
};
this.runEvents(evt, ev);
});
});
['keyup', 'keydown'].forEach(evt => {
window.addEventListener(evt, ev => {
this.runEvents(evt, ev);
this.runEvents(`${evt}:${ev.key.toLowerCase()}`, ev);
});
});
this.runEvents('created', {});
this.animate();
}
animate() {
requestAnimationFrame(this.animate.bind(this));
this.render();
}
render() {
this.runEvents('beforeUpdate', {});
this.renderer.render(this.scene, this.camera);
this.runEvents('updated', {});
}
on(type, callback) {
this.events.push({type, callback});
}
runEvents() {
var args = [].slice.call(arguments);
let type = args.shift();
this.events.forEach(evt => {
if (evt.type!=type) return;
evt.callback.apply(this, [this, ...args]);
});
}
raycastMouseIntersects(ev) {
let mouse = new THREE.Vector2();
mouse.x = ( ev.layerX / this.width ) * 2 - 1;
mouse.y = - ( ev.layerY / this.height ) * 2 + 1;
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
return raycaster.intersectObjects(this.scene.children);
}
jsonMerge(item1, item2) {
let _isObject = function(item) { return (item && typeof item === 'object' && !Array.isArray(item)); };
if (_isObject(item1) && _isObject(item2)) {
item1 = Object.assign({}, item1, item2);
for(let i in item1) {
item1[i] = this.jsonMerge(item1[i], item2[i]);
}
}
return item1;
}
}
/*
el: HTML element target
settings.gridHelper: Grid Helper. Accepts false, true or json {size:10, divisions:10}
settings.renderer: Renderer settings
https://threejs.org/docs/#api/en/renderers/WebGLRenderer
How to use:
import App from 'App.js';
new App({
el: document.querySelector('.element'),
events: [
{type:'create', callback: (app, ev) => {
console.log('create');
}},
{type:'update', callback: (app, ev) => {
console.log('update');
}},
{type:'click', callback: (app, ev) => {
console.log('click');
}},
{type:'update', callback: (app, ev) => {
console.log('update');
}},
],
});
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app" style="height:400px; border:solid 1px #e5e5e5;"></div>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three';
import { OrbitControls } from 'https://cdn.skypack.dev/three@/examples/jsm/controls/OrbitControls.js';
class App {
constructor(params={}) {
params = {
el: "body",
...params
};
if (typeof params.el=='string') {
params.el = document.querySelector(params.el);
}
this.el = params.el;
this.width = this.el.offsetWidth;
this.height = this.el.offsetHeight;
this.scene = Object.assign(new THREE.Scene(), {
background: new THREE.Color(0xeeffff),
});
this.camera = new THREE.PerspectiveCamera(40, this.width/this.height, 1, 100);
this.renderer = new THREE.WebGLRenderer( { antialias: true } );
// this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.width, this.height);
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.el.appendChild(this.renderer.domElement);
this.create();
setInterval(() => {
requestAnimationFrame(time => {
this.update();
this.renderer.render(this.scene, this.camera);
});
}, 1000/60);
window.addEventListener('resize', ev => {
this.width = this.el.offsetWidth;
this.height = this.el.offsetHeight;
this.camera.aspect = this.width/this.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.width, this.height);
});
}
create() {
this.camera.position.x = 5;
this.camera.position.y = 5;
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.scene.add(new THREE.GridHelper(10, 10));
const geometry = new THREE.TorusKnotGeometry(1, .2, 64, 8);
const material = new THREE.MeshBasicMaterial({
color: 0x000044,
});
this.scene.add(this.torusKnot = new THREE.Mesh(geometry, material));
}
update() {
this.torusKnot.rotation.y += .02;
}
}
new App({el: "#app"});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment