爪とぎ用。


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

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

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

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

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8" />
	<meta name="robots" content="noarchive" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
	<meta name="landscape" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
	<style>
html, body { margin: 0; padding: 0; }
	</style>
	<title>マルチタッチ サンプル</title>
</head>
<body>
	<p>3本の指で同時に線を描けます (※マウス不可)</p>
	<canvas id="canvas"></canvas>
	<script src="script.js"></script>
</body>
</html>
JavaScript
(() => {
	const canvas = document.getElementById('canvas');
	const _ctx = canvas.getContext('2d');

	// 接地中の指の情報を保持するためのオブジェクト (3本まで)
	const _fingers = [
		{ identifier: null, lastX: null, lastY: null }, // 指のID, 最終描画位置X, 最終描画位置Y
		{ identifier: null, lastX: null, lastY: null },
		{ identifier: null, lastX: null, lastY: null }
	];
	
	/**
	 * 新しい指情報を登録する
	 * @param {Number} Touchオブジェクトのidentifier
	 * @return {Number|Boolean} 成功すれば指番号、失敗すればfalse
	 */
	const addFinger = (newIdentifier) => {
		// 同じIDの指がすでに登録済みなら、何もせずに指番号を返却 (※ありえないと思うが念のため)
		const dupFingerIndex = _fingers.findIndex(finger => finger.identifier === newIdentifier);
		if (dupFingerIndex >= 0) {
			return dupFingerIndex;
		}
		
		// 同じIDの指が登録済みでなければ、空いているところに登録して指番号を返却
		for (let fingerIndex = 0; fingerIndex < _fingers.length; ++fingerIndex) {
			if (_fingers[fingerIndex].identifier === null) {
				_fingers[fingerIndex].identifier = newIdentifier;
				return fingerIndex;
			}
		}
		
		// すでに指がいっぱいならfalseを返却
		return false;
	};

	/**
	 * identifierを指定して、登録済みの指情報を検索する
	 * @param {Number} Touchオブジェクトのidentifier
	 * @return {Number|Boolean} 存在すれば指番号、しなければfalse
	 */
	const findFinger = (identifier) => {
		const fingerIndex = _fingers.findIndex(finger => finger.identifier === identifier);
		if (fingerIndex >= 0) {
			return fingerIndex;
		} else {
			return false;
		}
	};

	/**
	 * identifierを指定して、登録済みの指情報を削除する。
	 * @param {Number} Touchオブジェクトのidentifier
	 */
	const removeFinger = (identifier) => {
		const fingerIndex = findFinger(identifier);
		if (fingerIndex !== false) {
			_fingers[fingerIndex].identifier = null;
		}
	};

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

	/**
	 * 指番号を指定して、描画開始時の準備をする。
	 * @param {Number} 指番号
	 */
	const startDrawing = (fingerIndex) => {
		// 最終描画位置をクリア
		_fingers[fingerIndex].lastX = _fingers[fingerIndex].lastY = null;
	};

	/**
	 * 指番号と座標を指定して、Canvasに描画する。
	 * @param {Number} 指番号
	 * @param {Number} x
	 * @param {Number} y
	 */
	const draw = (fingerIndex, x, y) => {
		// 指情報取得
		const finger = _fingers[fingerIndex];

		// 最終描画位置が存在すればそこから、しなければ指定座標からパスを作成して線を引く
		_ctx.beginPath();
		if (finger.lastX === null) {
			_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の準備
	canvas.width  = document.documentElement.clientWidth - 2;
	canvas.height = 400;
	_ctx.strokeStyle = 'rgba(255, 0, 0, 1)'; // 線の色を赤に
	_ctx.fillStyle   = 'rgba(0, 0, 0, 1)';   // 塗りつぶし色を黒に
	_ctx.fillRect(0, 0, canvas.width, canvas.height); // 全体を塗りつぶし

	// 指が置かれたとき...
	canvas.addEventListener('touchstart', (e) => {
		Array.prototype.forEach.call(e.changedTouches, (touch) => {
			// 置かれた指を登録
			const fingerIndex = addFinger(touch.identifier);
			
			// 登録できればその位置に点を打つ
			if (fingerIndex !== false) {
				startDrawing(fingerIndex);
				const coords = getCoordsByTouch(touch);
				draw(fingerIndex, coords.x, coords.y);
			}
		});
	}); 

	// 指が離れたとき...
	canvas.addEventListener('touchend', (e) => {
		Array.prototype.forEach.call(e.changedTouches, (touch) => {
			// 離れた指を削除
			removeFinger(touch.identifier);
		});
	});

	// 指が動いたとき...
	canvas.addEventListener('touchmove', (e) => {
		e.preventDefault(); // スクロール防止
		Array.prototype.forEach.call(e.changedTouches, (touch) => {
			// 動いた指が登録済みの指の中にあるか探す
			const fingerIndex = findFinger(touch.identifier);
			
			// 登録済みならば、元の位置から新しい位置に向かって線を引く
			if (fingerIndex !== false) {
				const coords = getCoordsByTouch(touch);
				draw(fingerIndex, coords.x, coords.y);
			}
		});
	});
})();