ペイントツール - Javascript

  • 作成日:
  • 最終更新日:2025/08/06

簡単なペイントツールの作成

マウス操作とタッチイベントどちらにも対応したペイントツールです。

主な機能は、以下の通りです。

  • マウスとタッチイベントの両方に対応
  • 線の太さを選択可能
  • 色を選択可能
  • 描画内容のリセット
  • 描画内容を画像として保存可能

Code

HTML / CSS

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="/javascripts/paint.js"></script>
  <title>Document</title>
  <style>
    canvas {
        background-color: white;
        border: solid 5px black;
    }
    .black {
        background-color: black;
        color: white;
    }
    .white {
        background-color: white;
        color: black;
    }
    .red {
        background-color: red;
        color: white;
    }
    .blue {
        background-color: blue;
        color: white;
    }
    .yellow {
        background-color: yellow;
        color: black;
    }
  </style>
</head>
<body>
  <main>
    <p>文字の太さ:
      <select name="" id="fontweight">
        <option value="1">1</option>
        <option value="3" selected>3</option>
        <option value="5">5</option>
      </select>
    </p>
    <p>
      色選択: 
      <select id="color" class="black">
          <option value="black" class="black" selected>black</option>
          <option value="white" class="white">white</option>
          <option value="red" class="red">red</option>
          <option value="blue" class="blue">blue</option>
          <option value="yellow" class="yellow">yellow</option>
      </select>
    </p>
    <p><button id="reset">リセット</button></p>
    

    <canvas id="canvas" width="300" height="300"></canvas>

    <div>
      <h2>画像の保存</h2>
      <p>背景色
        <select name="" id="bgcolor">
          <option value="transparent">透過</option>
          <option value="white">white</option>
        </select>
        <button id="save">保存</button>
      </p>
    </div>
  </main>
</body>
</html>

Javascript

window.addEventListener('load', function() {
    // true: 描画中   false: 描画中でない
    let flgDraw = false;

    // 座標
    let gX = 0;
    let gY = 0;

    // 描画色
    let gColor = 'black';

    let fontWeight = 3;

    // イベント登録
    // マウス
    const canvas = document.getElementById('canvas');
    
    canvas.addEventListener('mousedown', startDraw, false);
    canvas.addEventListener('mousemove', Draw, false);
    canvas.addEventListener('mouseup', endDraw, false);

    // タッチ
    canvas.addEventListener('touchstart', startDraw, false);
    canvas.addEventListener('touchmove', Draw, false);
    canvas.addEventListener('touchend', endDraw, false);
    
    // セレクトボックス
    const s = document.getElementById('color');
    s.addEventListener('change', changeColor, false);

    const select_fontweight = document.getElementById('fontweight');
    select_fontweight.addEventListener('change', changefontWeight, false);

    const resetBtn = document.getElementById('reset');
    resetBtn.addEventListener('click', clearCanvas, false);

    const saveBtn = document.getElementById('save');
    saveBtn.addEventListener('click', saveImage, false);

    // セレクトボックス変更時に色を変更する
    function changeColor(){
        const select = document.getElementById('color');
        gColor = select.value;
        const selectedOption = select.options[select.selectedIndex];
        const computedStyle = window.getComputedStyle(selectedOption);
        const color = computedStyle.color
        const backgroundColor = computedStyle.backgroundColor;
        document.getElementById('color').style.color = color;
        document.getElementById('color').style.backgroundColor = backgroundColor;
    }

    // フォントウェイト変更時に線の太さを変更する
    function changefontWeight(){
        fontWeight = document.getElementById('fontweight').value;
    }

    function getXY(e) {
        const rect = canvas.getBoundingClientRect();
        let x, y;

        if (e.touches) {
            // タッチ操作時
            x = e.touches[0].clientX - rect.left;
            y = e.touches[0].clientY - rect.top;
        } else {
            // マウス操作時
            x = e.offsetX;
            y = e.offsetY;
        }

        return { x, y };
    }

    // 描画開始
    function startDraw(e) {
        const pos = getXY(e);
        gX = pos.x;
        gY = pos.y;
        flgDraw = true;
        if (e.touches) e.preventDefault(); // スクロール防止
    }

    // 描画
    function Draw(e) {
        if (!flgDraw) return;

        const con = canvas.getContext('2d');
        const pos = getXY(e);

        con.lineWidth = fontWeight;
        con.strokeStyle = gColor;
        con.beginPath();
        con.moveTo(gX, gY);
        con.lineTo(pos.x, pos.y);
        con.closePath();
        con.stroke();

        gX = pos.x;
        gY = pos.y;

        if (e.touches) e.preventDefault(); // スクロール防止
    }

    // 描画終了
    function endDraw(e) {
        flgDraw = false;
        if (e && e.touches) e.preventDefault(); // スクロール防止
    }

    // キャンバスをクリア
    function clearCanvas() {
        const con = canvas.getContext('2d');
        con.clearRect(0, 0, canvas.width, canvas.height);
    }

    // PNG保存処理
    function saveImage() {
        // 一時的なキャンバスを作成
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = canvas.width;
        tempCanvas.height = canvas.height;

        const bgColor = document.getElementById('bgcolor').value;

        const tempCtx = tempCanvas.getContext('2d');

        // 背景を白で塗りつぶす(透明防止)
        tempCtx.fillStyle = bgColor;
        tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);

        // 元のキャンバス内容をコピー
        tempCtx.drawImage(canvas, 0, 0);

        // データURLを取得
        const image = tempCanvas.toDataURL('image/png');

        // ダウンロード処理
        const link = document.createElement('a');
        link.href = image;
        link.download = 'canvas_drawing.png';
        link.click();
    }
});

セレクトメニューのスタイル操作

インラインスタイルで指定した場合

<select id="color" style="color:white;background-color:black">
    <option value="white" style="background-color:black;color:white" selected>black</option>
    <option value="white" style="background-color:white;color:black">white</option>
    <option value="red" style="background-color:red;color:white">red</option>
    <option value="blue" style="background-color:blue;color:white">blue</option>
    <option value="yellow" style="background-color:yellow;color:black">yellow</option>
</select>

上記の option の sytle 属性の値を javascript で select タグに反映させるには、以下のようにします。

function changeColor(){
    const select = document.getElementById('color');
    gColor = select.value;
    const selectedOption = select.options[select.selectedIndex];
    const color = selectedOption.style.color
    const backgroundColor = selectedOption.style.backgroundColor;
    document.getElementById('color').style.color = color;
    document.getElementById('color').style.backgroundColor = backgroundColor;
}
select.options 全ての <option> 要素が入った配列のようなオブジェクト
select.selectedIndex 現在選ばれている <option> のインデックス番号
selectedOption.style HTMLの style 属性から直接取得

外部CSSやクラスで指定されたスタイルを取得したい場合

CSSファイルや class で設定されたスタイルも含めて、最終的にブラウザが計算したスタイルを取得します。

<select id="color" class="black">
    <option value="black" class="black" selected>black</option>
    <option value="white" class="white">white</option>
    <option value="red" class="red">red</option>
    <option value="blue" class="blue">blue</option>
    <option value="yellow" class="yellow">yellow</option>
</select>
/* CSS */
.black {
    background-color: black;
    color: white;
}
.white {
    background-color: white;
    color: black;
}
.red {
    background-color: red;
    color: white;
}
.blue {
    background-color: blue;
    color: white;
}
.yellow {
    background-color: yellow;
    color: black;
}
function changeColor(){
    const select = document.getElementById('color');
    gColor = select.value;
    const selectedOption = select.options[select.selectedIndex];
    const computedStyle = window.getComputedStyle(selectedOption);
    const color = computedStyle.color
    const backgroundColor = computedStyle.backgroundColor;
    document.getElementById('color').style.color = color;
    document.getElementById('color').style.backgroundColor = backgroundColor;
}

タッチイベント

touchstart / touchmove イベントからはe.offsetX/Y が取得できないためtouches[0].clientXtouches[0].pageXを使う必要があります。

function getXY(e) {
    const rect = canvas.getBoundingClientRect();
    let x, y;

    if (e.touches) {
        // タッチ操作時
        x = e.touches[0].clientX - rect.left;
        y = e.touches[0].clientY - rect.top;
    } else {
        // マウス操作時
        x = e.offsetX;
        y = e.offsetY;
    }

    return { x, y };
}

タッチイベントで得られる clientX や clientY は 画面全体に対する座標のため Canvas 内の座標に変換するには、Canvas の「位置」を引く必要があります。

getBoundingClientRect() は、HTML要素の位置とサイズを取得するためのメソッドです。マウス操作なら offsetX / offsetY で済みますが、タッチ操作ではgetBoundingClientRect()が必要です。

以下の表は、戻り値の主なプロパティです。

プロパティ 意味
rect.top 要素の上端が、画面の上端からどれだけ離れているか(px)
rect.left 要素の左端が、画面の左端からどれだけ離れているか(px)
rect.right 要素の右端が、画面左端からの距離
rect.bottom 要素の下端が、画面上端からの距離
rect.width 要素の幅(px)
rect.height 要素の高さ(px)

画像の保存方法

以下は画像保存の最低限の機能を関数化する場合の例です。

function saveImage() {
    const image = canvas.toDataURL('image/png'); // PNG画像としてデータURLを取得
    const link = document.createElement('a');
    link.href = image;
    link.download = 'canvas_drawing.png'; // ファイル名
    link.click(); // 自動でダウンロード実行
}

canvas のリサイズ

function resizeCanvas() {
    const canvas = document.getElementById('canvas');
    const lineWidth = 12; // ボーダーの幅を考慮
    const dpr = window.devicePixelRatio || 1;
    const width = window.innerWidth - lineWidth;
    const height = window.innerHeight - lineWidth - 300;

    // 見た目サイズ(CSS)
    canvas.style.width = width + 'px';
    canvas.style.height = height + 'px';

    // 実際の描画領域サイズ(Retina対応)
    canvas.width = width * dpr;
    canvas.height = height * dpr;

    // コンテキストにスケーリング
    const ctx = canvas.getContext('2d');
    ctx.scale(dpr, dpr);
}
resizeCanvas(); // 読み込み時の初期リサイズ
window.addEventListener('resize', resizeCanvas);