taquaki-satwo
11/29/2017 - 2:58 PM

ペイントアプリ

ペイントアプリ

window.onload = function () {
  createPainter(document.body, 800, 600);
}

// 画面の生成
function createPainter(parent, width, height) {
  const title = elt('h2', null, 'Simple Painter');
  const [canvas, ctx] = createCanvas(width, height);
  const toolbar = elt('div', null);
  for (let name in controls) {
    toolbar.appendChild(controls[name](ctx));
  }
  toolbar.style.fontSize = 'small';
  toolbar.style.marginBottom = '3px';
  parent.appendChild(elt('div', null, title, toolbar, canvas));
}

function createCanvas(canvasWidth, canvasHeight) {
  const canvas = elt('canvas', { width: canvasWidth, height: canvasHeight });
  const ctx = canvas.getContext('2d');
  canvas.style.border = '1px solid gray';
  canvas.style.cursor = 'pointer';
  canvas.addEventListener('mousedown', function (e) {
    const event = document.createEvent('HTMLEvents');
    event.initEvent('change', false, true);
    colorInput.dispatchEvent(event);
    paintTools[paintTool](e, ctx);
  }, false);
  return [canvas, ctx];
}

function relativePosition(event, element) {
  const rect = element.getBoundingClientRect();
  return {
    x: Math.floor(event.clientX - rect.left),
    y: Math.floor(event.clientY - rect.top)
  };
}

let paintTool;
const paintTools = Object.create(null);
paintTools.brush = function (e, ctx) {
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  const p = relativePosition(e, ctx.canvas);
  ctx.beginPath();
  ctx.moveTo(p.x, p.y);
  setDragListeners(ctx, img, function (q) {
    ctx.lineTo(q.x, q.y);
    ctx.stroke();
  });
};
paintTools.line = function (e, ctx) {
  ctx.lineCap = 'round';
  const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  const p = relativePosition(e, ctx.canvas);
  setDragListeners(ctx, img, function (q) {
    ctx.beginPath();
    ctx.moveTo(p.x, p.y);
    ctx.lineTo(q.x, q.y);
    ctx.stroke();
  });
}
paintTools.cricle = function (e, ctx) {
  const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  const p = relativePosition(e, ctx.canvas);
  setDragListeners(ctx, img, function (q) {
    const dx = q.x - p.x;
    const dy = q.y - p.y;
    const r = Math.sqrt(dx * dx + dy * dy);
    ctx.beginPath();
    ctx.arc(p.x, p.y, r, 0, 2 * Math.PI, false);
    ctx.stroke();
  })
}
paintTools.circleFill = function (e, ctx) {
  const img = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  const p = relativePosition(e, ctx.canvas);
  setDragListeners(ctx, img, function (q) {
    const dx = q.x - p.x;
    const dy = q.y - p.y;
    const r = Math.sqrt(dx * dx + dy * dy);
    ctx.beginPath();
    ctx.arc(p.x, p.y, r, 0, 2 * Math.PI, false);
    ctx.fill();
  });
}

function setDragListeners(ctx, img, draw) {
  const mousemoveEventListener = function (e) {
    ctx.putImageData(img, 0, 0);
    draw(relativePosition(e, ctx.canvas));
  };
  document.addEventListener('mousemove', mousemoveEventListener, false);
  const mouseupEventListener = function (e) {
    ctx.putImageData(img, 0, 0);
    draw(relativePosition(e, ctx.canvas));
    document.removeEventListener('mousemove', mousemoveEventListener, false);
    document.removeEventListener('mouseup', mouseupEventListener, false);
  };
  document.addEventListener('mouseup', mouseupEventListener, false);
}

const controls = Object.create(null);
let colorInput;
controls.painter = function (ctx) {
  const DEFAULT_TOOL = 0;
  const select = elt('select', null);
  const label = elt('label', null, '描画ツール', select);
  for (let name in paintTools) {
    select.appendChild(elt('option', { value: name }, name));
  }
  select.selectedIndex = DEFAULT_TOOL;
  paintTool = select.children[DEFAULT_TOOL].value;
  select.addEventListener('change', function (e) {
    paintTool = this.children[this.selectedIndex].value;
  }, false);
  return label;
}
controls.color = function (ctx) {
  const input = colorInput = elt('input', { type: 'color' });
  const label = elt('label', null, '色:', input);
  input.addEventListener('change', function (e) {
    ctx.strokeStyle = this.value;
    ctx.fillStyle = this.value;
  }, false);
  return label;
};
controls.brushsize = function (ctx) {
  const size = [1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 20, 24, 28];
  const select = elt('select', null);
  for (let i = 0; i < size.length; i++) {
    select.appendChild(elt('option', { value: size[i].toString() }, size[i].toString()));
  }
  select.selectedIndex = 2;
  ctx.lineWidth = size[select.selectedIndex];
  const label = elt('label', null, '線幅:', select);
  select.addEventListener('change', function (e) {
    ctx.lineWidth = this.value;
  }, false);
  return label;
};
controls.alpha = function (ctx) {
  const input = elt('input', { type: 'number', min: '0', max: '1', step: '0.05', value: '1' });
  const label = elt('label', null, '透明度:', input);
  input.addEventListener('change', function (e) {
    ctx.globalAlpha = this.value;
  }, false);
  return label;
}

function elt(name, attributes) {
  const node = document.createElement(name);
  if (attributes) {
    for (let attr in attributes) {
      if (attributes.hasOwnProperty(attr)) {
        node.setAttribute(attr, attributes[attr]);
      }
    }
  }
  for (let i = 2; i < arguments.length; i++) {
    let child = arguments[i];
    if (typeof child == 'string') {
      child = document.createTextNode(child);
    }
    node.appendChild(child);
  }
  return node;
}

ペイントアプリ

A Pen by Takaaki Sato on CodePen.

License.