こうこく
作 ▸
改 ▸

マルチタッチを実装してCanvasに3本指で線を引くサンプル

爪とぎ用。

JavaScriptでマルチタッチ実装するサンプルです。複数本の指でCanvasに線を引けて、途中で指を増やしたり減らしたりしても大丈夫なはずです。

指が接地してるかぎり、Touch.identifier が変わらないのを利用してます。指が接地したときに identifier を覚えておき、指が離れたときに覚えておいた identifier を削除します。よって、実装上保持できるようにした identifier の数 = マルチタッチ可能な上限数ということになります。

ちなみに identifier は、Chrome (ver65.0.3325.181) の開発者ツールだとゼロが設定されてました。手元のiPhone 6Sだと、結構大きい桁数の連番だったような気がします。なのでゼロが来ることは一応考慮しておき、かつ identifier の具体的な数字に依存した実装も避けます。

しかしこれ Canvas でゲーム作るときに自分で無理やり作ったやつをキレイにしただけなので、穴があるかもしれません。

複数本の指で同時に線を描けます (※マウス不可)
HTML
<div style="margin:2rem 0; padding:7px; border:1px solid gray; border-radius: 3px; text-align:center;">
  <div style="margin-bottom:1rem;">複数本の指で同時に線を描けます (※マウス不可)</div>
  <canvas id="multitouch-canvas" style="border:1px solid gray;"></canvas>
</div>
JavaScript
// Canvasの準備
const canvas = document.getElementById('multitouch-canvas');
const _ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
_ctx.strokeStyle = 'rgba(255, 0, 0, 1)'; // 線の色を赤に
_ctx.fillStyle = 'rgba(0, 0, 0, 1)'; // 塗りつぶし色を黒に
_ctx.fillRect(0, 0, canvas.width, canvas.height); // 全体を塗りつぶし

/**
 * 接地中の指
 * @type {{ identifier: number, lastX: number, lastY: number }[]} 指のID, 最終描画位置X, 最終描画位置Y
 */
let _fingers = [];

/**
 * Touchオブジェクトから要素内での座標を算出
 * @param {Touch} TouchEventのchangedTouchesから取得したTouch
 * @return {{x: number; y: number}} x座標, y座標
 */
const getXYByTouch = (touch) => {
  const bounds = touch.target.getBoundingClientRect();
  return {
    x: touch.clientX - bounds.left,
    y: touch.clientY - bounds.top,
  };
};

/**
 * 指番号と座標を指定して、Canvasに描画する。
 * @param {{ identifier: number, lastX: number, lastY: number }} finger 指
 * @param {number} x
 * @param {number} y
 */
const draw = (finger, x, y) => {
  // 最終描画位置があればそこから、無ければ指定座標からパスを作成して線を引く
  _ctx.beginPath();
  if (finger.lastX === x && finger.lastY === y) {
    _ctx.moveTo(x - 1, y); // 移動量がゼロだと線が引けないので開始点を1pxだけずらす
  } else {
    _ctx.moveTo(finger.lastX, finger.lastY);
  }
  _ctx.lineTo(x, y);
  _ctx.closePath();
  _ctx.stroke();

  // 最終描画位置をとっておく
  finger.lastX = x;
  finger.lastY = y;
};

// 指が置かれたとき...
canvas.addEventListener('touchstart', (e) => {
  for (const touch of e.changedTouches) {
    const { x, y } = getXYByTouch(touch);

    // 接地した指を保存
    // 同じIDの指がすでに接地していれば無視 (ありえないと思うが念のため)
    let fingerIndex = _fingers.findIndex((finger) => finger.identifier === touch.identifier);
    if (fingerIndex < 0) {
      fingerIndex = _fingers.push({ identifier: touch.identifier, lastX: x, lastY: y }) - 1;
    }

    // 点を打つ
    draw(_fingers[fingerIndex], x, y);
  }
});

// 指が離れたとき...
canvas.addEventListener('touchend', (e) => {
  for (const touch of e.changedTouches) {
    // 離れた指を削除
    _fingers = _fingers.filter((finger) => finger.identifier !== touch.identifier);
  }
});

// 指が動いたとき...
canvas.addEventListener('touchmove', (e) => {
  e.preventDefault(); // スクロール防止
  for (const touch of e.changedTouches) {
    // 動いた指が登録済みの指の中にあれば、元の位置から新しい位置に向かって線を引く
    const fingerIndex = _fingers.findIndex((finger) => finger.identifier === touch.identifier);
    if (fingerIndex >= 0) {
      const { x, y } = getXYByTouch(touch);
      draw(_fingers[fingerIndex], x, y);
    }
  }
});
この記事に何かあればこちらまで (非公開)