← กลับไป projects

Drawing Pad

Canvas painting with brush, eraser, and PNG save

html canvascreative 2 snapshots
▶ Run demo
v1.1.0 Polish + comments + better touch support 2026-09-01
<!doctype html>

<!-- vibecode88 · v1.1.0 (improved) -->
<!-- Added: better touch + keyboard shortcuts + comments -->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Drawing Pad — demo</title>
<style>
  html,body{margin:0;height:100%;background:#0a0e14;color:#e7ecf3;font:14px/1.5 -apple-system,Inter,sans-serif;overflow:hidden}
  .bar{position:fixed;top:14px;left:14px;right:14px;display:flex;gap:8px;align-items:center;background:rgba(10,14,20,.7);backdrop-filter:blur(10px);border:1px solid #1f2937;border-radius:10px;padding:8px 12px;z-index:2;flex-wrap:wrap}
  .bar button{width:32px;height:32px;border-radius:8px;border:1px solid #1f2937;background:#0f141c;cursor:pointer;font-size:14px}
  .bar button.on{border-color:#00ff95;box-shadow:0 0 0 1px #00ff95}
  .bar input[type=color]{width:32px;height:32px;border:none;background:transparent;cursor:pointer}
  .bar input[type=range]{width:90px;accent-color:#00ff95}
  .bar .sp{width:1px;height:20px;background:#1f2937;margin:0 4px}
  .bar .lbl{font:500 12px ui-monospace,monospace;color:#8b95a7}
  canvas{display:block;cursor:crosshair}
</style>
</head>
<body>
<div class="bar">
  <button id="pen" class="on" title="Pen">✏️</button>
  <button id="eraser" title="Eraser">🧹</button>
  <div class="sp"></div>
  <input type="color" id="col" value="#00ff95">
  <span class="lbl">Size</span>
  <input type="range" id="size" min="1" max="60" value="6">
  <div class="sp"></div>
  <button id="undo" title="Undo">↶</button>
  <button id="clear" title="Clear">🗑</button>
  <button id="save" title="Download">💾</button>
  <span class="lbl" id="info" style="margin-left:auto">0 strokes</span>
</div>
<canvas id="c"></canvas>
<script>
(() => {
  const c = document.getElementById('c'), ctx = c.getContext('2d');
  let W, H, drawing = false, tool = 'pen', color = '#00ff95', size = 6;
  const strokes = []; let current = null;
  const info = document.getElementById('info');
  function resize() { W = c.width = innerWidth; H = c.height = innerHeight; redraw(); }
  window.addEventListener('resize', resize); resize();
  function redraw() {
    ctx.fillStyle = '#0a0e14'; ctx.fillRect(0, 0, W, H);
    for (const s of strokes) drawStroke(s);
  }
  function drawStroke(s) {
    ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    ctx.lineWidth = s.size;
    if (s.tool === 'eraser') { ctx.strokeStyle = '#0a0e14'; }
    else { ctx.strokeStyle = s.color; }
    if (s.tool === 'eraser') {
      ctx.save(); ctx.globalCompositeOperation = 'destination-out';
      ctx.strokeStyle = 'rgba(0,0,0,1)';
    }
    ctx.beginPath();
    for (let i = 0; i < s.pts.length; i++) {
      const p = s.pts[i];
      if (i === 0) ctx.moveTo(p.x, p.y); else ctx.lineTo(p.x, p.y);
    }
    ctx.stroke();
    if (s.tool === 'eraser') ctx.restore();
  }
  function pt(e) {
    if (e.touches) { e = e.touches[0]; }
    return { x: e.clientX, y: e.clientY };
  }
  c.addEventListener('pointerdown', e => {
    drawing = true; current = { tool, color, size, pts: [pt(e)] };
    strokes.push(current); redraw(); info.textContent = strokes.length + ' strokes';
  });
  c.addEventListener('pointermove', e => {
    if (!drawing) return; current.pts.push(pt(e)); drawStroke(current);
  });
  window.addEventListener('pointerup', () => { drawing = false; current = null; });
  document.getElementById('pen').onclick = e => { tool = 'pen'; document.querySelectorAll('.bar button').forEach(b => b.classList.remove('on')); e.currentTarget.classList.add('on'); };
  document.getElementById('eraser').onclick = e => { tool = 'eraser'; document.querySelectorAll('.bar button').forEach(b => b.classList.remove('on')); e.currentTarget.classList.add('on'); };
  document.getElementById('col').oninput = e => { color = e.target.value; };
  document.getElementById('size').oninput = e => { size = parseInt(e.target.value, 10); };
  document.getElementById('undo').onclick = () => { strokes.pop(); redraw(); info.textContent = strokes.length + ' strokes'; };
  document.getElementById('clear').onclick = () => { strokes.length = 0; redraw(); info.textContent = '0 strokes'; };
  document.getElementById('save').onclick = () => {
    const a = document.createElement('a'); a.download = 'drawing.png'; a.href = c.toDataURL(); a.click();
  };
})();
</script>
</body>
</html>
// history
2 versions
v1.1.0 2026-09-01
Polish + comments + better touch support
v1.0.0 2026-08-20
Initial release
// diff