javascript

examples

examples.js
/**
 * 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,
  };
}
Examples - JavaScript Tutorial | DeepML