A WebGL-based CRT terminal renderer for XTerm.js with authentic retro effects
npm install cool-retro-term-rendererA WebGL-based CRT terminal renderer for XTerm.js. This library provides authentic retro CRT visual effects for terminal applications, including screen curvature, phosphor glow, scanlines, and more.


Live demo available at https://remojansen.github.io/
``bash`
npm install cool-retro-term-renderer
This library requires three and @xterm/xterm as peer dependencies:
`bash`
npm install three @xterm/xterm
`typescript
import { CRTTerminal } from 'cool-retro-term-renderer';
import { Terminal } from '@xterm/xterm';
// Create a container element
const container = document.getElementById('terminal')!;
// Initialize the CRT renderer
const crt = new CRTTerminal({ container });
// Create and configure your XTerm instance
const xterm = new Terminal({
cols: 80,
rows: 24,
cursorBlink: false,
});
// XTerm needs a DOM element to attach to (can be hidden)
const hiddenContainer = document.createElement('div');
hiddenContainer.style.position = 'absolute';
hiddenContainer.style.left = '-9999px';
document.body.appendChild(hiddenContainer);
xterm.open(hiddenContainer);
// Attach XTerm to the CRT renderer
crt.attachXTerm(xterm);
// Now use XTerm as normal - output will render with CRT effects
xterm.write('Hello, CRT World!\r\n');
xterm.write('$ ');
`
For more control over the rendering pipeline (e.g., custom Three.js scenes, audio integration, or custom terminal adapters), you can use the lower-level components directly:
`typescript
import * as THREE from 'three';
import { TerminalFrame, TerminalText } from 'cool-retro-term-renderer';
import { Terminal } from '@xterm/xterm';
const container = document.getElementById('terminal')!;
// Create your own Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
camera.position.z = 1;
// Create the renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x000000);
container.appendChild(renderer.domElement);
// Create the terminal text renderer (handles character rendering and effects)
const terminalText = new TerminalText(window.innerWidth, window.innerHeight);
terminalText.mesh.position.z = 0;
scene.add(terminalText.mesh);
// Create the terminal frame (CRT bezel/border)
const terminalFrame = new TerminalFrame(window.innerWidth, window.innerHeight);
terminalFrame.mesh.position.z = 0.1;
scene.add(terminalFrame.mesh);
// Create XTerm instance (hidden, used as input handler)
const xterm = new Terminal({ cols: 80, rows: 24 });
const hiddenContainer = document.createElement('div');
hiddenContainer.style.position = 'absolute';
hiddenContainer.style.left = '-9999px';
document.body.appendChild(hiddenContainer);
xterm.open(hiddenContainer);
// Sync XTerm dimensions with the calculated terminal grid size
const gridSize = terminalText.getGridSize();
if (gridSize.cols > 0 && gridSize.rows > 0) {
xterm.resize(gridSize.cols, gridSize.rows);
}
// Listen for grid size changes and resize XTerm
terminalText.onGridSizeChange((cols, rows) => {
if (cols > 0 && rows > 0) {
xterm.resize(cols, rows);
}
});
// Handle window resize
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
terminalFrame.updateSize(window.innerWidth, window.innerHeight);
terminalText.updateSize(window.innerWidth, window.innerHeight);
});
// Animation loop
function animate() {
terminalText.updateTime(performance.now());
terminalText.renderStaticPass(renderer);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
`
| Export | Description |
|--------|-------------|
| TerminalText | Renders terminal characters with CRT effects (shaders, bloom, etc.) |TerminalFrame
| | Renders the CRT monitor bezel/frame |XTermConnector
| | Syncs an XTerm.js buffer to TerminalText (optional helper) |
| Method | Description |
|--------|-------------|
| getGridSize() | Get terminal dimensions { cols, rows } |onGridSizeChange(callback)
| | Register callback for grid size changes |updateSize(width, height)
| | Update renderer dimensions |updateTime(time)
| | Update time uniform for animated effects |renderStaticPass(renderer)
| | Render effects that don't change per-frame |setFontColor(color)
| | Set font color (hex string) |setBackgroundColor(color)
| | Set background color (hex string) |setScreenCurvature(value)
| | Set screen curvature (0-1) |setBloom(value)
| | Set bloom intensity (0-1) |setBrightness(value)
| | Set brightness (0-1) |setFlickering(value)
| | Set flickering intensity (0-1) |setStaticNoise(value)
| | Set static noise (0-1) |setBurnIn(value)
| | Set burn-in persistence (0-1) |setRasterizationMode(mode)
| | Set scanline mode (0-3) |dispose()
| | Clean up resources |
The main class that creates the WebGL renderer and manages the CRT effect pipeline.
#### Constructor
`typescript`
new CRTTerminal(options: CRTTerminalSettings)
#### CRTTerminalSettings
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| container | HTMLElement | required | The container element to render into |fontColor
| | string | "#0ccc68" | Font color in hex format (green) |backgroundColor
| | string | "#000000" | Background color in hex format |screenCurvature
| | number | 0.3 | Screen curvature amount (0-1) |rgbShift
| | number | 0 | RGB shift/chromatic aberration (0-0.01) |bloom
| | number | 0.5538 | Bloom intensity (0-1) |brightness
| | number | 0.5 | Brightness level (0-1) |ambientLight
| | number | 0.2 | Ambient light glow (0-1) |chromaColor
| | number | 0 | Chroma color (0=mono, 1=full color) |flickering
| | number | 0.1 | Flickering intensity (0-1) |horizontalSync
| | number | 0.08 | Horizontal sync distortion (0-1) |jitter
| | number | 0.1997 | Jitter/displacement (0-1) |staticNoise
| | number | 0.1198 | Static noise intensity (0-1) |glowingLine
| | number | 0.2 | Scanning beam intensity (0-1) |burnIn
| | number | 0.2517 | Phosphor burn-in persistence (0-1) |rasterizationMode
| | number | 1 | 0=none, 1=scanline, 2=pixel, 3=subpixel |rasterizationIntensity
| | number | 0.5 | Scanline intensity (0-1) |
#### Methods
| Method | Description |
|--------|-------------|
| attachXTerm(xterm: Terminal) | Attach an XTerm.js terminal instance |detachXTerm()
| | Detach the currently attached XTerm instance |getGridSize()
| | Get the terminal grid size { cols, rows } |focus()
| | Focus the attached XTerm terminal |dispose()
| | Clean up all resources |
#### Advanced Access
For advanced usage, you can access the underlying components:
`typescript`
crt.getTerminalText() // TerminalText renderer
crt.getRenderer() // THREE.WebGLRenderer
crt.getScene() // THREE.Scene
crt.getCamera() // THREE.OrthographicCamera
The library implements a two-pass rendering pipeline that replicates the visual characteristics of CRT monitors:
`typescript`
const crt = new CRTTerminal({
container,
fontColor: '#0ccc68',
bloom: 0.6,
screenCurvature: 0.3,
});
`typescript`
const crt = new CRTTerminal({
container,
fontColor: '#ffb000',
bloom: 0.5,
burnIn: 0.3,
});
`typescript`
const crt = new CRTTerminal({
container,
screenCurvature: 0.4,
flickering: 0.15,
horizontalSync: 0.1,
staticNoise: 0.15,
burnIn: 0.3,
rasterizationMode: 1,
rasterizationIntensity: 0.6,
});
`typescript`
const crt = new CRTTerminal({
container,
screenCurvature: 0,
flickering: 0,
horizontalSync: 0,
jitter: 0,
staticNoise: 0,
burnIn: 0,
rasterizationMode: 0,
});
Requires WebGL support. Works in all modern browsers:
- Chrome 56+
- Firefox 51+
- Safari 15+
- Edge 79+
`bashInstall dependencies
npm install
GPL-3.0
This project is a port to WebGL from cool-retro-term by Filippo Scognamiglio.
This library uses the Terminus Font by Dimitar Zhekov, licensed under the SIL Open Font License (OFL).