Productivity

HANDWRITING

A digital notepad for handwriting and drawing. Write with your mouse, stylus, or touchscreen.

Tip: Use this as a scratchpad for quick notes, sketches, or signatures. Works best with a touchscreen or drawing tablet.
Width
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

const canvas = document.getElementById('noteCanvas');
const ctx = canvas.getContext('2d');
const linesCanvas = document.getElementById('linesCanvas');
const linesCtx = linesCanvas.getContext('2d');

let penColor = '#000000', penWidth = 3, currentTool = 'pen';
let drawing = false, lastX, lastY;
let strokes = [], currentStroke = [];
let history = [], historyIdx = -1;

function initCanvas() {
  const w = canvas.parentElement.clientWidth;
  const h = 400;
  canvas.width = w; canvas.height = h;
  linesCanvas.width = w; linesCanvas.height = h;
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, w, h);
  drawLines();
  saveState();
}

function drawLines() {
  linesCtx.clearRect(0, 0, linesCanvas.width, linesCanvas.height);
  linesCtx.strokeStyle = '#cbd5e1';
  linesCtx.lineWidth = 1;
  for (let y = 36; y < linesCanvas.height; y += 32) {
    linesCtx.beginPath();
    linesCtx.moveTo(0, y);
    linesCtx.lineTo(linesCanvas.width, y);
    linesCtx.stroke();
  }
}

function getPos(e) {
  const r = canvas.getBoundingClientRect();
  const x = (e.clientX || (e.touches && e.touches[0].clientX)) - r.left;
  const y = (e.clientY || (e.touches && e.touches[0].clientY)) - r.top;
  return { x, y };
}

function startDraw(e) {
  drawing = true;
  const p = getPos(e);
  lastX = p.x; lastY = p.y;
  ctx.beginPath();
  ctx.moveTo(p.x, p.y);
}

function moveDraw(e) {
  if (!drawing) return;
  const p = getPos(e);
  ctx.strokeStyle = currentTool === 'eraser' ? '#ffffff' : penColor;
  ctx.lineWidth = currentTool === 'eraser' ? penWidth * 4 : penWidth;
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  ctx.beginPath();
  ctx.moveTo(lastX, lastY);
  ctx.lineTo(p.x, p.y);
  ctx.stroke();
  lastX = p.x; lastY = p.y;
}

function endDraw() {
  if (drawing) { drawing = false; saveState(); }
}

canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('mousemove', moveDraw);
canvas.addEventListener('mouseup', endDraw);
canvas.addEventListener('mouseleave', endDraw);
canvas.addEventListener('touchstart', e => { e.preventDefault(); startDraw(e); }, { passive: false });
canvas.addEventListener('touchmove', e => { e.preventDefault(); moveDraw(e); }, { passive: false });
canvas.addEventListener('touchend', endDraw);

function setTool(t) {
  currentTool = t;
  document.querySelectorAll('.tb-btn[data-tool]').forEach(b => b.classList.toggle('active', b.dataset.tool === t));
}

function setColor(c) {
  penColor = c;
  document.querySelectorAll('.tb-color').forEach(b => b.classList.toggle('active', b.dataset.color === c));
}

function setWidth(w) {
  penWidth = w;
}

function saveState() {
  historyIdx++;
  history = history.slice(0, historyIdx);
  history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
  if (history.length > 40) { history.shift(); historyIdx--; }
}

function undo() {
  if (historyIdx > 0) { historyIdx--; ctx.putImageData(history[historyIdx], 0, 0); }
}

function clearCanvas() {
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  saveState();
}

function exportPNG() {
  const a = document.createElement('a');
  a.download = 'handwriting.png';
  a.href = canvas.toDataURL('image/png');
  a.click();
}

function exportWithNotes() {
  const notes = document.getElementById('textNotes').value;
  const exportCanvas = document.createElement('canvas');
  exportCanvas.width = canvas.width;
  const textLines = notes ? notes.split('\n') : [];
  const textHeight = textLines.length > 0 ? textLines.length * 22 + 40 : 0;
  exportCanvas.height = canvas.height + textHeight;
  const ectx = exportCanvas.getContext('2d');
  ectx.fillStyle = '#ffffff';
  ectx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
  ectx.drawImage(canvas, 0, 0);
  if (textLines.length > 0) {
    ectx.fillStyle = '#f8f9fa';
    ectx.fillRect(0, canvas.height, exportCanvas.width, textHeight);
    ectx.fillStyle = '#1a1a1a';
    ectx.font = '14px monospace';
    textLines.forEach((line, i) => {
      ectx.fillText(line, 16, canvas.height + 24 + i * 22);
    });
  }
  const a = document.createElement('a');
  a.download = 'handwriting-notes.png';
  a.href = exportCanvas.toDataURL('image/png');
  a.click();
}

window.addEventListener('resize', () => {
  const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const w = canvas.parentElement.clientWidth;
  canvas.width = w; linesCanvas.width = w;
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.putImageData(img, 0, 0);
  drawLines();
});

initCanvas();