#問題12
中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
円をドラッグで動かせるようにしなさい。
円内をダウンした時のみ円を動かせること。
円のドラッグ中にマウスカーソルがcanvasの外に出た場合、円をドラッグを終了すること。
なお、以下のHTMLを使うこと。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題12</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
$('#my-canvas').mousedown(e => {
});
$('#my-canvas').mousemove(e => {
});
$('#my-canvas').mouseup(e => {
});
$('#my-canvas').mouseout(e => {
});
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>
#解答
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題12</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {
const RADIUS = 50; // 円の半径
let posX = 150, // 円の中心のX座標
posY = 150, // 円の中心のY座標
dragFlag = false, // 円をドラッグ中か
deltaX = 0,
deltaY = 0;
drawCircle(posX, posY);
$('#my-canvas').mousedown(e => {
// canvasの左上隅を原点とする座標を求める
const downX = e.offsetX,
downY = e.offsetY;
const ret = hitCircle(posX, posY, RADIUS, downX, downY);
if(!ret) {// 円内をダウンしていないので処理終了
dragFlag = false;
return;
}
// 円内をダウンしたので、フラグをtrueにする
dragFlag = true;
// ダウン位置を始点とし、円の中心位置を終点とするベクトルを求める
deltaX = posX - downX;
deltaY = posY - downY;
});
$('#my-canvas').mousemove(e => {
if(!dragFlag) {// 円がドラッグ中でない
return;
}
// canvasの左上隅を原点とする座標を求める
const downX = e.offsetX,
downY = e.offsetY;
// 円の中心位置を更新する
posX = downX + deltaX;
posY = downY + deltaY;
// 円を描画する
drawCircle(posX, posY);
});
$('#my-canvas').mouseup(e => {
// マウスボタンが離されたので、ドラッグ中フラグをfalseにする
dragFlag = false;
});
$('#my-canvas').mouseout(e => {
// マウスカーソルがcanvasの外に出たので、ドラッグ中フラグをfalseにする
dragFlag = false;
});
// 円内をダウンしたか調べる
function hitCircle(posX, posY, radius, downX, downY) {
// (posX, posY) と (downX, downY) の距離を求める
const dist = Math.sqrt((posX - downX) * (posX - downX) + (posY - downY) * (posY - downY));
if(dist <= radius) {// 円内
return true;
} else {// 円外
return false;
}
}
function drawCircle(posX, posY) {
// コンテキストを取得
const ctx = $('#my-canvas')[0].getContext('2d');
// 現在の描画状態を保存する
ctx.save();
// canvasをクリア
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 塗りつぶしの色をマゼンタ色にする
ctx.fillStyle = '#ff00ff';
ctx.setTransform(1, 0, 0, 1, posX, posY);
ctx.beginPath(); // 現在のパスをリセットする
ctx.arc(0, 0, RADIUS, 0, Math.PI*2, true); // 円を描画する
ctx.closePath(); // パスを閉じる
ctx.fill(); // 現在のパスを塗りつぶす
// 描画状態を保存した時点のものに戻す
ctx.restore();
}
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>
#解説
みなさん、プログラムは少し慣れてきたと思うので
今回はアルゴリズムベース(どういう風に実現するか)で説明していきます。
まずは、マウスダウン時に円の中か外であるか判定する必要があります。
単純の距離を計算して、それが50以内かどうかで判定しましょう。(hitCircleメソッドに該当)
$(x_1,y_1)$と$(x_2,y_2)$の距離は$\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$で計算できます。
次に、円の中をダウンした場合、ドラッグ中であるフラグを立てて、
ダウン位置から円の中心位置までのベクトルを求めます。
これは次に発生するであろうmosuemove(マウスを動かしたとき)時に円の座標を更新するためです。
下図を見てください。
左円がmousedown時、真ん中と右がmousemove時です。
mousemove時に取得できる座標は水色のxですが、更新するのは緑色の丸の円の中心座標ですので
その差分を足してあげる必要があります。
最後に円を描画します。
これは問題10,問題11とほぼ同じです。
これで終わりといいたいところですが
mouseup(マウスボタンを離した時)に円のドラッグを終了させたいので
ドラッグ中のフラグをfalseにする必要があります。
また、mouseout(canvasの外にマウスカーソル出た)時も
ドラッグ中のフラグをfalseにします。
#補足
解答では、mousemoveで円を描画していますが
mousemove時に描画するのではなく、
mousemove時は円の位置情報だけ更新しておいて、requesAnimationFrameで描画すると、
パフォーマンスが良くなります。(問題11参照)
知っていて損はないテクニックです。