TypeScript library providing modular wrappers and utilities for Three.js and other utility function for every day work. Built with type safety, dependency injection pattern, and testing.
- 🎯 Modular Architecture: Clean, reusable components for common Three.js patterns
- 🔧 Dependency Injection: Accept Three.js classes as parameters for optimal tree-shaking
- 📝 Full TypeScript Support: Strict typing with comprehensive type definitions
- 🎨 Shader Integration: Built-in uniforms system for seamless GLSL integration
- 🧪 Thoroughly Tested: 184+ unit tests with 95%+ coverage across all modules
- 📦 Multiple Formats: ES modules, CommonJS, and UMD builds available
npm install github:Jonathan-J8/joeat-utils#dist-only
# or
npx degit github:Jonathan-J8/joeat-utils#dist-only ./joeat-utils# Install specific version (recommended for production)
npm install github:Jonathan-J8/joeat-utils#v1.2.2
# Using degit
npx degit github:Jonathan-J8/joeat-utils#v1.2.2 ./joeat-utils{
"dependencies": {
"joeat-utils": "github:Jonathan-J8/joeat-utils#v1.2.2"
}
}Find the example here
npm run devMost modules follow a consistent API pattern for predictable usage:
const module = new ModuleName({
instance: threeJsObject, // Three.js object to wrap
...dependencies, // Required Three.js classes for tree-shaking
});resize({ width, height, pixelRatio })- Handle responsive updatesupdate({ time, deltaTime, deltaMs })- Animation loop integrationdebug(gui: GUI)- Runtime debugging with lil-gui controlsclear()- Clean disposal and garbage collection
module.instance- Access to the wrapped Three.js objectmodule.uniforms- Shader uniforms for GLSL integration
const myEmitter = new MonoEventEmitter<[{ someNumber: number }]>();
const foo = ({ someNumber }) => {
// your logic
};
myEmitter.addListener(foo);
myEmitter.fire({ someNumber: 10 });
myEmitter.removeListener(foo);
myEmitter.clear();import { Animator, RendererWrapper, PointerTracker, CameraWrapper } from '../../src';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// Create renderer with post-processing support
const renderer = new THREE.WebGLRenderer();
const rendererWrapper = new RendererWrapper({
instance: renderer,
Vector2: THREE.Vector2,
});
// Set up cameras with controls
const perspectiveCamera = new THREE.PerspectiveCamera(75, 2, 0.1, 1000);
const orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
const controls = new OrbitControls(perspectiveCamera, renderer.domElement);
const cameraWrapper = new CameraWrapper({
perspective: perspectiveCamera,
orthographic: orthographicCamera,
controls: controls,
Vector3: THREE.Vector3,
Quaternion: THREE.Quaternion,
});
// Set up animation loop
const animator = new Animator();
animator.addListener(({ time, deltaTime }) => {
cameraWrapper.update({ deltaTime });
rendererWrapper.update(scene, cameraWrapper.instance, deltaTime);
});
// Add mouse interaction
const pointerTracker = new PointerTracker({
camera: cameraWrapper.instance,
Vector2: THREE.Vector2,
Vector3: THREE.Vector3,
Plane: THREE.Plane,
Raycaster: THREE.Raycaster,
});
pointerTracker.init(renderer.domElement);
// Handle resize
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
cameraWrapper.resize({ width, height });
rendererWrapper.resize({ width, height, pixelRatio: window.devicePixelRatio });
});
animator.play();Central animation loop with renderer integration support.
const animator = new Animator();
// Basic animation loop
animator.addListener(({ time, deltaTime }) => {
// Update your scene
});
// Value interpolation
animator.interpolate({
from: 0,
to: 100,
duration: 2000,
onUpdate: ({ value }) => {
mesh.position.x = value;
},
});
// Animation
animator.animate({
steps: 0,
duration: 400,
delay: 100,
iterations: 1,
onUpdate: () => {
// your animation logic
},
});
animator.play();WebGL renderer abstraction with post-processing effects support.
const rendererWrapper = new RendererWrapper({
instance: new THREE.WebGLRenderer(),
Vector2: THREE.Vector2,
EffectComposer: EffectComposer, // Optional for post-processing
});
// Access uniforms for shaders
const uniforms = {
uTime: animator.uniforms.uTime,
uResolution: rendererWrapper.uniforms.uResolution,
};
// Add post-processing effects
rendererWrapper.addEffect(bloomPass, fxaaPass);
// Handle resize
rendererWrapper.resize({
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: window.devicePixelRatio,
});Mouse/pointer interaction handling with coordinate normalization.
const camera = new THREE.PerspectiveCamera();
const pointerTracker = new PointerTracker({
camera: camera,
Vector2: THREE.Vector2,
Vector3: THREE.Vector3,
Plane: THREE.Plane,
Raycaster: THREE.Raycaster,
});
// Initialize with element
pointerTracker.init(canvas);
// Access normalized coordinates and world position
const uniforms = {
uPointerPosition: pointerTracker.uniforms.uPointerPosition, // [-1, 1]
uPointerWorldPosition: pointerTracker.uniforms.uPointerWorldPosition, // World coordinates
uPointerPositionVelocity: pointerTracker.uniforms.uPointerPositionVelocity,
uPointerPress: pointerTracker.uniforms.uPointerPress, // Pointer pressure
uPointerScroll: pointerTracker.uniforms.uPointerScroll,
uPointerScrollVelocity: pointerTracker.uniforms.uPointerScrollVelocity,
};Responsive behavior with ResizeObserver integration.
const resizer = new Resizer(canvas);
resizer.addListener(({ width, height, pixelRatio }) => {
renderer.setSize(width, height, false);
renderer.setPixelRatio(pixelRatio);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
// Configure quality settings
resizer.resolutionFactor = 0.8; // Reduce resolution for performance
resizer.maxSize = 2048; // Limit maximum dimensionsAdvanced camera management with dual-camera system, controls integration, and comprehensive shader uniforms.
const perspectiveCamera = new THREE.PerspectiveCamera(75, 2, 0.1, 1000);
const orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
const controls = new OrbitControls(perspectiveCamera, canvas);
const cameraWrapper = new CameraWrapper({
perspective: perspectiveCamera,
orthographic: orthographicCamera,
controls: controls, // OrbitControls, FlyControls, ArcballControls, or DragControls
Vector3: THREE.Vector3,
Quaternion: THREE.Quaternion,
});
// Access comprehensive uniforms for shaders
const uniforms = {
uCameraDirection: cameraWrapper.uniforms.cameraDirection, // Camera world direction
uCameraScale: cameraWrapper.uniforms.cameraScale, // Camera world scale
uCameraQuaternion: cameraWrapper.uniforms.cameraQuaternion, // Camera world rotation
};
// Switch between camera types at runtime
// default cameraWrapper.instance === controls.object
cameraWrapper.instance = 'OrthographicCamera'; // Switch to orthographic
cameraWrapper.instance = 'PerspectiveCamera'; // Switch back to perspective
// Handle resize (updates both cameras)
cameraWrapper.resize({
width: window.innerWidth,
height: window.innerHeight,
});
// Update camera uniforms and controls
cameraWrapper.update({ deltaTime: 0.016 });
// Debug camera in lil-gui
cameraWrapper.debug(gui); // Adds camera position, type toggle, and controls settings
// Clean up resources
cameraWrapper.clear(); // Disposes controls and clears cameras- Dual Camera System: Seamlessly switch between perspective and orthographic cameras
- Controls Integration: Support for OrbitControls, FlyControls, ArcballControls, and DragControls
- Smart Initialization: Automatically detects initial camera type from controls object
- Comprehensive Uniforms: Direction, scale, and quaternion vectors for advanced shader effects
- Responsive Design: Handles both perspective aspect ratio and orthographic bounds calculation
- Debug Interface: Runtime camera controls with position tracking and controls configuration
- Conditional Updates: Controls only update when enabled, optimizing performance
Sequential task execution with async support.
const taskQueue = new TaskQueue();
taskQueue.add(
() => console.log('First task'),
async () => {
await fetch('/api/data');
console.log('Async task completed');
},
() => console.log('Final task'),
);
await taskQueue.play(); // Executes all tasks in sequencePhysics-based animation system.
const spring = new Spring(0, {
onUpdate: ({ value }) => {
// animate any value
},
});
spring.to(1);
spring.update(); // Call in your animation loopScene resource management with automatic disposal.
const sceneWrapper = new SceneWrapper({
scene: new THREE.Scene(),
});
// Automatic cleanup of materials, geometries, textures
sceneWrapper.clear();Type-safe single-event emitter base class.
class MyAnimator extends MonoEventEmitter<[{ progress: number }]> {
update() {
this.fire({ progress: 0.5 });
}
}
const animator = new MyAnimator();
animator.addListener(({ progress }) => {
console.log(`Progress: ${progress * 100}%`);
});All wrapper classes provide uniforms for seamless GLSL integration:
// Time and rendering uniforms
uniform float uTime;
uniform float uDeltaTime;
uniform vec2 uResolution;
// Pointer interaction uniforms
uniform vec2 uMouse;
uniform vec2 uPointerPositionVelocity;
uniform float uPointerPress;
uniform vec2 uPointerScroll;
// Camera uniforms (from CameraWrapper)
uniform vec3 uCameraDirection;
uniform vec3 uCameraScale;
uniform vec4 uCameraQuaternion;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
// Animated effect using time
float wave = sin(uv.x * 10.0 + uTime * 2.0);
// Mouse interaction
float mouse = length(uv - uMouse * 0.5 + 0.5);
// Camera-based effects
float cameraInfluence = dot(uCameraDirection, vec3(0.0, 1.0, 0.0));
// Combine effects
vec3 color = vec3(wave, mouse, cameraInfluence);
gl_FragColor = vec4(color, 1.0);
}# Install dependencies
npm install
# Build library
npm run build
# Run tests
npm test
# Lint and format
npm run lint
npm run format- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
All commits are validated with ESLint + Prettier code quality checks and unit tests