javascript
examples
examples.js⚡javascript
/**
* Canvas and WebGL Examples
*
* Demonstrates 2D canvas graphics and WebGL basics
*/
// =============================================================================
// 1. Canvas Setup
// =============================================================================
/**
* Setup canvas with proper DPI handling
*/
function setupCanvas(canvas, width, height) {
const dpr = window.devicePixelRatio || 1;
// Set display size
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// Set actual size in memory
canvas.width = width * dpr;
canvas.height = height * dpr;
// Scale context to match
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
return ctx;
}
/**
* Create an off-screen canvas for pre-rendering
*/
function createOffscreenCanvas(width, height) {
if (typeof OffscreenCanvas !== 'undefined') {
return new OffscreenCanvas(width, height);
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
// =============================================================================
// 2. Basic Shapes
// =============================================================================
class ShapeDrawer {
constructor(ctx) {
this.ctx = ctx;
}
/**
* Draw a filled rectangle
*/
fillRect(x, y, width, height, color = 'black') {
this.ctx.fillStyle = color;
this.ctx.fillRect(x, y, width, height);
}
/**
* Draw a stroked rectangle
*/
strokeRect(x, y, width, height, color = 'black', lineWidth = 1) {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
this.ctx.strokeRect(x, y, width, height);
}
/**
* Draw a circle
*/
circle(x, y, radius, fill = true, color = 'black') {
this.ctx.beginPath();
this.ctx.arc(x, y, radius, 0, Math.PI * 2);
if (fill) {
this.ctx.fillStyle = color;
this.ctx.fill();
} else {
this.ctx.strokeStyle = color;
this.ctx.stroke();
}
}
/**
* Draw an ellipse
*/
ellipse(x, y, radiusX, radiusY, rotation = 0, fill = true, color = 'black') {
this.ctx.beginPath();
this.ctx.ellipse(x, y, radiusX, radiusY, rotation, 0, Math.PI * 2);
if (fill) {
this.ctx.fillStyle = color;
this.ctx.fill();
} else {
this.ctx.strokeStyle = color;
this.ctx.stroke();
}
}
/**
* Draw a line
*/
line(x1, y1, x2, y2, color = 'black', lineWidth = 1) {
this.ctx.beginPath();
this.ctx.moveTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
this.ctx.stroke();
}
/**
* Draw a polygon
*/
polygon(points, fill = true, color = 'black') {
if (points.length < 3) return;
this.ctx.beginPath();
this.ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
this.ctx.lineTo(points[i].x, points[i].y);
}
this.ctx.closePath();
if (fill) {
this.ctx.fillStyle = color;
this.ctx.fill();
} else {
this.ctx.strokeStyle = color;
this.ctx.stroke();
}
}
/**
* Draw a regular polygon (e.g., hexagon, pentagon)
*/
regularPolygon(
centerX,
centerY,
radius,
sides,
fill = true,
color = 'black'
) {
const points = [];
const angleStep = (Math.PI * 2) / sides;
for (let i = 0; i < sides; i++) {
const angle = i * angleStep - Math.PI / 2; // Start from top
points.push({
x: centerX + radius * Math.cos(angle),
y: centerY + radius * Math.sin(angle),
});
}
this.polygon(points, fill, color);
}
/**
* Draw a rounded rectangle
*/
roundedRect(x, y, width, height, radius, fill = true, color = 'black') {
this.ctx.beginPath();
this.ctx.moveTo(x + radius, y);
this.ctx.lineTo(x + width - radius, y);
this.ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
this.ctx.lineTo(x + width, y + height - radius);
this.ctx.quadraticCurveTo(
x + width,
y + height,
x + width - radius,
y + height
);
this.ctx.lineTo(x + radius, y + height);
this.ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
this.ctx.lineTo(x, y + radius);
this.ctx.quadraticCurveTo(x, y, x + radius, y);
this.ctx.closePath();
if (fill) {
this.ctx.fillStyle = color;
this.ctx.fill();
} else {
this.ctx.strokeStyle = color;
this.ctx.stroke();
}
}
}
// =============================================================================
// 3. Gradients and Patterns
// =============================================================================
class GradientManager {
constructor(ctx) {
this.ctx = ctx;
}
/**
* Create linear gradient
*/
linear(x0, y0, x1, y1, colorStops) {
const gradient = this.ctx.createLinearGradient(x0, y0, x1, y1);
colorStops.forEach(({ offset, color }) => {
gradient.addColorStop(offset, color);
});
return gradient;
}
/**
* Create radial gradient
*/
radial(x0, y0, r0, x1, y1, r1, colorStops) {
const gradient = this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
colorStops.forEach(({ offset, color }) => {
gradient.addColorStop(offset, color);
});
return gradient;
}
/**
* Create conic gradient (if supported)
*/
conic(startAngle, x, y, colorStops) {
if (!this.ctx.createConicGradient) {
console.warn('Conic gradients not supported');
return null;
}
const gradient = this.ctx.createConicGradient(startAngle, x, y);
colorStops.forEach(({ offset, color }) => {
gradient.addColorStop(offset, color);
});
return gradient;
}
/**
* Create pattern from image
*/
pattern(image, repetition = 'repeat') {
return this.ctx.createPattern(image, repetition);
}
}
// =============================================================================
// 4. Text Rendering
// =============================================================================
class TextRenderer {
constructor(ctx) {
this.ctx = ctx;
}
/**
* Set font properties
*/
setFont(size, family = 'sans-serif', weight = 'normal') {
this.ctx.font = `${weight} ${size}px ${family}`;
}
/**
* Draw filled text
*/
fillText(text, x, y, options = {}) {
const {
color = 'black',
align = 'left',
baseline = 'alphabetic',
maxWidth,
} = options;
this.ctx.fillStyle = color;
this.ctx.textAlign = align;
this.ctx.textBaseline = baseline;
if (maxWidth) {
this.ctx.fillText(text, x, y, maxWidth);
} else {
this.ctx.fillText(text, x, y);
}
}
/**
* Draw stroked text
*/
strokeText(text, x, y, options = {}) {
const {
color = 'black',
lineWidth = 1,
align = 'left',
baseline = 'alphabetic',
} = options;
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
this.ctx.textAlign = align;
this.ctx.textBaseline = baseline;
this.ctx.strokeText(text, x, y);
}
/**
* Measure text dimensions
*/
measure(text) {
const metrics = this.ctx.measureText(text);
return {
width: metrics.width,
actualBoundingBoxAscent: metrics.actualBoundingBoxAscent,
actualBoundingBoxDescent: metrics.actualBoundingBoxDescent,
height:
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,
};
}
/**
* Word wrap text
*/
wrapText(text, x, y, maxWidth, lineHeight) {
const words = text.split(' ');
let line = '';
const lines = [];
for (let i = 0; i < words.length; i++) {
const testLine = line + words[i] + ' ';
const metrics = this.ctx.measureText(testLine);
if (metrics.width > maxWidth && i > 0) {
lines.push(line.trim());
line = words[i] + ' ';
} else {
line = testLine;
}
}
lines.push(line.trim());
lines.forEach((line, index) => {
this.ctx.fillText(line, x, y + index * lineHeight);
});
return lines.length;
}
}
// =============================================================================
// 5. Image Operations
// =============================================================================
class ImageHandler {
constructor(ctx) {
this.ctx = ctx;
}
/**
* Load an image
*/
async load(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
/**
* Draw image
*/
draw(image, x, y, width, height) {
if (width && height) {
this.ctx.drawImage(image, x, y, width, height);
} else {
this.ctx.drawImage(image, x, y);
}
}
/**
* Draw cropped image
*/
drawCropped(image, sx, sy, sw, sh, dx, dy, dw, dh) {
this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
}
/**
* Get image data
*/
getImageData(x, y, width, height) {
return this.ctx.getImageData(x, y, width, height);
}
/**
* Put image data
*/
putImageData(imageData, x, y) {
this.ctx.putImageData(imageData, x, y);
}
/**
* Apply grayscale filter
*/
grayscale(x, y, width, height) {
const imageData = this.ctx.getImageData(x, y, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // Red
data[i + 1] = avg; // Green
data[i + 2] = avg; // Blue
}
this.ctx.putImageData(imageData, x, y);
}
/**
* Apply brightness adjustment
*/
brightness(x, y, width, height, amount) {
const imageData = this.ctx.getImageData(x, y, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.min(255, Math.max(0, data[i] + amount));
data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + amount));
data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + amount));
}
this.ctx.putImageData(imageData, x, y);
}
}
// =============================================================================
// 6. Transformations
// =============================================================================
class TransformHelper {
constructor(ctx) {
this.ctx = ctx;
this.stack = [];
}
/**
* Save current state
*/
save() {
this.ctx.save();
}
/**
* Restore previous state
*/
restore() {
this.ctx.restore();
}
/**
* Translate origin
*/
translate(x, y) {
this.ctx.translate(x, y);
}
/**
* Rotate (degrees)
*/
rotate(degrees) {
this.ctx.rotate((degrees * Math.PI) / 180);
}
/**
* Rotate around point
*/
rotateAround(x, y, degrees) {
this.ctx.translate(x, y);
this.ctx.rotate((degrees * Math.PI) / 180);
this.ctx.translate(-x, -y);
}
/**
* Scale
*/
scale(sx, sy = sx) {
this.ctx.scale(sx, sy);
}
/**
* Scale from point
*/
scaleFrom(x, y, sx, sy = sx) {
this.ctx.translate(x, y);
this.ctx.scale(sx, sy);
this.ctx.translate(-x, -y);
}
/**
* Reset transformation
*/
reset() {
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
}
}
// =============================================================================
// 7. Animation Framework
// =============================================================================
class AnimationLoop {
constructor(canvas, drawCallback) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.drawCallback = drawCallback;
this.running = false;
this.lastTime = 0;
this.fps = 0;
this.frameCount = 0;
this.lastFpsUpdate = 0;
}
/**
* Start animation loop
*/
start() {
if (this.running) return;
this.running = true;
this.lastTime = performance.now();
this.lastFpsUpdate = this.lastTime;
this.loop();
}
/**
* Stop animation loop
*/
stop() {
this.running = false;
}
/**
* Main animation loop
*/
loop = (currentTime = performance.now()) => {
if (!this.running) return;
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
// Update FPS counter
this.frameCount++;
if (currentTime - this.lastFpsUpdate >= 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastFpsUpdate = currentTime;
}
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Call draw callback
this.drawCallback(this.ctx, deltaTime, currentTime);
// Request next frame
requestAnimationFrame(this.loop);
};
/**
* Get current FPS
*/
getFPS() {
return this.fps;
}
}
// =============================================================================
// 8. Particle System Example
// =============================================================================
class Particle {
constructor(x, y, options = {}) {
this.x = x;
this.y = y;
this.vx = options.vx || (Math.random() - 0.5) * 4;
this.vy = options.vy || (Math.random() - 0.5) * 4;
this.radius = options.radius || Math.random() * 3 + 1;
this.color = options.color || `hsl(${Math.random() * 360}, 70%, 50%)`;
this.life = options.life || 1;
this.decay = options.decay || 0.02;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= this.decay;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.globalAlpha = this.life;
ctx.fill();
ctx.globalAlpha = 1;
}
isDead() {
return this.life <= 0;
}
}
class ParticleSystem {
constructor(ctx) {
this.ctx = ctx;
this.particles = [];
}
emit(x, y, count = 10, options = {}) {
for (let i = 0; i < count; i++) {
this.particles.push(new Particle(x, y, options));
}
}
update() {
this.particles = this.particles.filter((p) => {
p.update();
return !p.isDead();
});
}
draw() {
this.particles.forEach((p) => p.draw(this.ctx));
}
}
// =============================================================================
// 9. WebGL Basic Setup
// =============================================================================
class WebGLRenderer {
constructor(canvas) {
this.canvas = canvas;
this.gl =
canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!this.gl) {
throw new Error('WebGL not supported');
}
}
/**
* Create shader
*/
createShader(type, source) {
const gl = this.gl;
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
/**
* Create program
*/
createProgram(vertexSource, fragmentSource) {
const gl = this.gl;
const vertexShader = this.createShader(gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this.createShader(
gl.FRAGMENT_SHADER,
fragmentSource
);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
/**
* Set viewport
*/
setViewport(
x = 0,
y = 0,
width = this.canvas.width,
height = this.canvas.height
) {
this.gl.viewport(x, y, width, height);
}
/**
* Clear with color
*/
clear(r = 0, g = 0, b = 0, a = 1) {
const gl = this.gl;
gl.clearColor(r, g, b, a);
gl.clear(gl.COLOR_BUFFER_BIT);
}
/**
* Create buffer
*/
createBuffer(data) {
const gl = this.gl;
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
return buffer;
}
}
// =============================================================================
// 10. Usage Example
// =============================================================================
function runCanvasDemo() {
// Create canvas
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const ctx = setupCanvas(canvas, 800, 600);
// Create helpers
const shapes = new ShapeDrawer(ctx);
const gradients = new GradientManager(ctx);
const text = new TextRenderer(ctx);
const particles = new ParticleSystem(ctx);
// Animation
const animation = new AnimationLoop(canvas, (ctx, deltaTime) => {
// Draw gradient background
const bgGradient = gradients.linear(0, 0, 0, 600, [
{ offset: 0, color: '#1a1a2e' },
{ offset: 1, color: '#16213e' },
]);
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, 800, 600);
// Draw shapes
shapes.circle(400, 300, 50, true, '#e94560');
shapes.regularPolygon(200, 200, 40, 6, true, '#0f3460');
shapes.roundedRect(500, 400, 100, 60, 10, true, '#533483');
// Update and draw particles
particles.update();
particles.draw();
// Draw FPS
text.setFont(14, 'monospace');
text.fillText(`FPS: ${animation.getFPS()}`, 10, 20, { color: '#fff' });
});
// Emit particles on click
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
particles.emit(x, y, 20);
});
animation.start();
}
// =============================================================================
// Export
// =============================================================================
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
setupCanvas,
createOffscreenCanvas,
ShapeDrawer,
GradientManager,
TextRenderer,
ImageHandler,
TransformHelper,
AnimationLoop,
Particle,
ParticleSystem,
WebGLRenderer,
};
}