view source

JavaScript

document.title = 'JavaScript Canvasでお絵かき';

$(function(){
  var w = document.body.clientWidth;
  var h = window.innerHeight;
  var padding = 10;
  var r = 80;
  var cx = r+padding;
  var cy = r+padding;
  var isMouseDown = false;
  var sysWidth = 120;
  var $demo = $('#demo');
  var $po = $('<div>',{id:'pointer'});
  var $sys = $('<div>',{id:'sys'}).css({width:sysWidth});
  w = w - sysWidth;
  var canvas = $('<canvas>',{width:w, height:h});
  canvas[0].width = w;
  canvas[0].height = h;
  $('#demo').append(canvas);
  $demo
    .append($po)
    .append($sys);
  var ctx = canvas[0].getContext('2d');

  var thickness = 3;
  var color = '#000000';
  var bgcolor = '#eeeeee';
  var mode = 'pen';
  var isErase = false;


  const STACK_MAX_SIZE = 5;
  let undoDataStack = [];
  let redoDataStack = [];

  var $clear = $('<button>',{title:'クリア'}).text('CLEAR');
  var $undo = $('<button>',{title:'戻す'}).text('UNDO');
  var $dl = $('<button>',{title:'ダウンロード'}).text('DOWNLOAD');
  var $wid = $('<input>',{type:'range',min:0.5,max:16,title:'線の太さ'}).val(thickness);
  var $color = $('<input>',{type:'color',value:color,title:'色'});
  var $bgcolor = $('<input>',{type:'color',value:bgcolor,title:'塗り潰し'});
  var $mode = $('<p>').text('-');
  var $erase = $('<button>',{mode:'erase',title:'消しゴム'}).text('□');
  var $modePen = $('<button>',{mode:'pen',title:'ペン'}).text('~').addClass('clicked');
  var $modeLine = $('<button>',{mode:'line',title:'直線'}).text('ー');
  var $modeCircle = $('<button>',{mode:'circle',title:'円'}).text('●');
  var $modeRect = $('<button>',{mode:'rect',title:'短形'}).text('■');
  var $modeSplay = $('<button>',{mode:'spray',title:'スプレー'}).text('▒▒');

  var modeBtns = [
    $modePen,
    $modeLine,
    $modeCircle,
    $modeRect,
    $modeSplay,
  ];
  $sys
    .append($clear)
    .append($undo)
    .append($dl)
    .append($mode)
    .append($wid)
    .append($color)
    .append($bgcolor)
    .append($erase);
  
  $clear.on('click', function(){
    ctx.clearRect(0, 0, w, h);
  });
  $undo.on('click', function(){
    undo();
  });
  $dl.on('click', function(){
    var data = canvas[0].toDataURL('image/png');
    var a = $('<a>', {href:data,download:'canvas'});
    a[0].click();
  });
  $color.on('change', function(){
    color = $(this).val();
    ctx.globalCompositeOperation = 'source-over';
    isErase = false;
    $('.on').removeClass('on');
  });
  $wid.on('change', function(){
    thickness = $(this).val();
  });
  modeBtns.forEach(function(btn){
    btn.on('click', function(){
      $('.clicked').removeClass('clicked');
      $(this).addClass('clicked');
      mode = $(this).attr('mode');
      $mode.text(mode);
    });
    $sys.append(btn)
  });

  $bgcolor.on('change', function(){
    bgcolor = $(this).val();
    ctx.fillStyle = bgcolor;
    ctx.fillRect(0, 0, w, h);
    ctx.fillStyle = color;
  });
  $erase.on('click', function(){
    isErase = !isErase;
    if(isErase){
      ctx.globalCompositeOperation = 'destination-out';
      $(this).addClass('on');
      $('[mode]').addClass('on');
    } else {
      ctx.globalCompositeOperation = 'source-over';
      $(this).removeClass('on');
      $('[mode]').removeClass('on');
    }
  });
  function evTouch(ee){
    var e = ee;
    var ex;
    var ey;

    if(e.touches){
      var touch = e.touches[0] || e.changedTouches[0];
      ex = touch.clientX;
      ey = touch.clientY;
    } else {
      ex = e.offsetX;
      ey = e.offsetY;
    }
    var x = ex;
    var y = ey;
    return {x:x,y:y};
  }

  function pointerOut(e) {
    $po.css({
      opacity:0,
    });
  }
  function pointerMove(e) {
    var ee = evTouch(e);
    var think = thickness+2;
    if(think<3){think=3;}
    $po.css({
      opacity:1,
      left:ee.x,
      top:ee.y,
      backgroundColor:color,
      width:think,
      height:think,
      margin:-thickness/2,
    });
  }
  function mouseDown(e) {
    beforeDraw();
    isMouseDown = true;
    var ee = evTouch(e);
    x = ee.x;
    y = ee.y;
  }
  function mouseUp(e) {
    isMouseDown = false;
    var zz = evTouch(e);
    var ee = {x:x, y:y};
    if(mode==='circle'){
      drawCircle(ee,zz);
    }
    if(mode==='rect'){
      drawRect(ee,zz);
    }
    if(mode==='line'){
      drawLine(ee,zz);
    }
    //var ee = evTouch(e);
    //x = ee.x;
    //y = ee.y;
  }
  function mouseMove(e) {
    var ee = evTouch(e);
    if(isMouseDown && (mode==='pen')){
      drawPen(ee);
    }
    if(isMouseDown && (mode==='spray')){
      x = ee.x;
      y = ee.y;
    }
  }
  function mouseClick(e) {
    var ee = evTouch(e);
  }
  function drawPen(e) {
    ctx.lineWidth = thickness;
    ctx.strokeStyle = color;
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    //ctx.globalCompositeOperation = 'source-over';
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(e.x, e.y);
    ctx.stroke();
    ctx.closePath();
    x = e.x;
    y = e.y;
  }
  function drawLine(e,z) {
    ctx.lineWidth = thickness;
    ctx.strokeStyle = color;
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(e.x, e.y);
    ctx.lineTo(z.x, z.y);
    ctx.stroke();
    ctx.closePath();
    x = e.x;
    y = e.y;
  }
  function drawCircle(e,z) {
    var a = z.x-e.x;
    var b = z.y-e.y;
    var cc = Math.pow(Math.abs(a),2)+Math.pow(Math.abs(b),2);
    var r = Math.pow(cc, 1/2);
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.arc(e.x, e.y, r, 0, Math.PI*2, false);
    ctx.fill();
  }
  function drawRect(e,z) {
    var w = z.x-e.x;
    var h = z.y-e.y;
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.fillRect(e.x, e.y, w, h);
    ctx.fill();
  }

  function drawSplay(e){
    var l = Math.floor(Math.random() * 8)+1;
    for(i=0;i<l;i++){
      drawSplayX(e);
    }
  }
  function drawSplayX(e) {
    //console.log({x:x,y:y,mode:mode});
    //var crx = (Math.random() + Math.random() + Math.random() + Math.random())/4;
    //var cry = (Math.random() + Math.random() + Math.random() + Math.random())/4;
    var rr = (Math.random() * Math.random()) * thickness * 2/3;
    var rLen = (Math.random() * Math.random()) * thickness*4;
    var rRad = Math.random() * Math.PI *2;
    var rx = Math.cos(rRad)*rLen;
    var ry = Math.sin(rRad)*rLen;
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.arc(x+rx, y+ry, rr, 0, Math.PI*2, false);
    ctx.fill();
  }
  function checkMouse(e,z) {
    if(!isMouseDown){return;}
    if(mode==='spray'){
      drawSplay();
    }
  }

  function beforeDraw(){
    //https://qiita.com/ampersand/items/69c8d632ed9f60358418
    redoDataStack = [];
    if (undoDataStack.length >= STACK_MAX_SIZE) {
        undoDataStack.pop();
    }
    var data = ctx.getImageData(0, 0, w, h);
    undoDataStack.unshift(data);
  }

  function undo(){
    if (undoDataStack.length <= 0) return;
    var data = ctx.getImageData(0, 0, w, h);
    redoDataStack.unshift(data);
    var imageData = undoDataStack.shift();
    ctx.putImageData(imageData, 0, 0);
  }

  function redo(){
    if (redoDataStack.length <= 0) return;
    var data = ctx.getImageData(0, 0, w, h);
    undoDataStack.unshift(data);
    var imageData = redoDataStack.shift();
    ctx.putImageData(imageData, 0, 0);
  }

  canvas[0].addEventListener('mousemove', pointerMove);
  $sys[0].addEventListener('mousemove', pointerOut);
  canvas[0].addEventListener('mouseout', pointerOut);
  canvas[0].addEventListener('mousedown', mouseDown);
  canvas[0].addEventListener('mousemove', mouseMove);
  canvas[0].addEventListener('click', mouseClick);
  canvas[0].addEventListener('mouseup', mouseUp);
  canvas[0].addEventListener('touchstart', mouseDown);
  canvas[0].addEventListener('touchmove', mouseMove);
  canvas[0].addEventListener('touchend', mouseUp);
  canvas[0].addEventListener('touchstart', function(){
    $('html,body').css({overflow:'hidden'});
  });
  setInterval(function(){
    checkMouse();
  },3);


  var inputFile = document.createElement('input');
  inputFile.type = 'file';
  inputFile.setAttribute('accept', 'image/*');
  $sys.append(inputFile);
  inputFile.onchange = function() {
    let file = this.files[0];
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function() {
      img.src = reader.result;
    }
  }
  var img = document.createElement('img');
  var src = img.src;
  var  dragArea = document.body;
  dragArea.ondragstart = function(event){
    if(!event.dataTranfer){return;}
    event.dataTranfer.addElement(container);
  }
  dragArea.ondragover = function(event){
    event.preventDefault();
  }
  dragArea.ondragleave = function(event){
    event.preventDefault();
  }
  dragArea.ondrop = function(event){
    event.preventDefault();
    console.log(event.dataTransfer.files);
    if(!event.dataTransfer.files.length){return;}
    var file = event.dataTransfer.files[0];
    fileNameDef = file.name.substr(0,6);

    var reader = new FileReader();
    reader.readAsDataURL(file)

    reader.onload = function(){
      img.src = reader.result;
    }
  }
  img.onload = function () {
    readyCanvasImg();
  };
  img.src = src;
  var readyCanvasImg = function(){
    var width = img.naturalWidth || 400;
    var height = img.naturalHeight || 400;
    console.log({
      width:width,
      height:height,
    });
    canvas.css({
      width:width,
      height:height,
    });

    canvas[0].width = width;
    canvas[0].height = height;
    ctx.clearRect(0, 0, width, height); 
    var x = (canvas[0].width - img.width) / 2;
    var y = (canvas[0].height - img.height) / 2;      
    ctx.drawImage(img, x, y);
  }













});

CSS

#demo canvas{
  cursor:none;
}
#sys{
  display:block;
  position:fixed;
  right:0;
  top:0;
  width:120px;
  height:100%;
  z-index:3;
  background-color:#eee;
}

#demo div button{
  display:block;
  width:100%;
  height:40px;
}
#demo div button.clicked{
  background-color:#fcc;
}
#demo div button.on{
  background-color:#fff;
}
#demo div button.on[mode]{
  color:#fff;
  background-color:#555;
}
#demo div input{
  display:block;
  width:100%;
  height:40px;
}

#pointer{
  position:absolute;
  width:3px;
  height:3px;
  background-color:#000;
  top:0;
  left:0;
  z-index:1;
  border-radius:50%;
  cursor:none;
  pointer-events: none;
}

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/demo/-js/js-canvas-oekaki/

ABOUT

hi0a.com 「ひまアプリ」は無料でつかえる便利ツールやブラウザ上で遊べるミニゲームを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。ご了承下さい。