Canvasイベント操作まとめ

#はじめに
前回Canvasアニメーションの要点という記事を投稿しました。
今回はCanvasでのイベント操作について調べたものをまとめます。

> Canvasアニメーションの要点
> http://qiita.com/nekoneko-wanwan/items/33afa5d20264c83b2bd1


# 今回のイベント範囲
ここで取り上げるイベントの種類は以下です。
（ひとまずtouchイベントは置いておきます...）

- click
- double click
- mouse over / out
- mouse down / up
- mouse move
- mouse wheel
- key up / down

> なおそれぞれのコードについては個人的にポイントと思う箇所のみを記述します。実際の動き（全てのコード）は↓ページで確認ができます
> http://nekoneko-wanwan.github.io/demo/canvas/event/


# サンプルコード
イベント登録は以下のように設定します。
※IE8以前は、addEventListener()に未対応ですが、ここでは考慮していません

```sample.js
function action() {
    var canvas = document.getElementById('e_click');
    var context = canvas.getContext('2d');

    function eventHandler() {
        draw();
    }

    function draw() {
        // 描画処理
        context.fillRect(...);
    }

    /* canvas要素に対してイベントを設定 */
    canvas.addEventListener('event', eventHandler, false);
}
action();
```

# 各イベントの要点

## clickイベント

- クリックした座標の取得には、getBoundingClientRect()とclientX / Yの取得が必要

![anim_click.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/984683c5-79ec-bc0f-4ea1-9ddd03ea2530.gif)

```click.js
var x = 0;
var y = 0;
function onClick(e) {
    /*
     * rectでcanvasの絶対座標位置を取得し、
     * クリック座標であるe.clientX,e.clientYからその分を引く
     * ※クリック座標はdocumentからの位置を返すため
     * ※rectはスクロール量によって値が変わるので、onClick()内でつど定義
     */
    var rect = e.target.getBoundingClientRect();
    x = e.clientX - rect.left;
    y = e.clientY - rect.top;

    draw();
}

function draw() {
    // 描画処理
    ctx.fillRect(x, y, 10, 10);
}

canvas.addEventListener('click', onClick, false);
```

## double clickイベント

- clickイベントとほぼ同じなので省略


## mouse over / out イベント（Canvas全体に対する）

- canvasに対してmouseover / out のイベントを設定するだけ

![anim_over-out.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/7f2bae28-2083-e448-0023-edaf1988c2cb.gif)


```mouseover-out.js
function onMouseOver() {
    draw();
}
function onMouseOut() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function draw() {
    // 描画処理
}
canvas.addEventListener('mouseover', onMouseOver, false);
canvas.addEventListener('mouseout', onMouseOut, false);
```


## mouse over / out イベント（Canvas内要素に対する）

- Canvasは1画像のため、そのままmouse overでは中の要素のみを対象とはできない
- つど要素との当たり判定を行う、別途Canvasを用意する、など一工夫が必要（のハズ...）
- ここでは当たり判定を採用

![anim_over-out2.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/5b1f2fa9-8bea-2ccb-ed27-77e6f5296739.gif)

```mouseover-out2.js
var targetFlag = false; // trueでマウスが要素に乗っているとみなす
var rect = null;

/* Canvas上にマウスが乗った時 */
function onMouseOver(e) {
    rect = e.target.getBoundingClientRect();
    canvas.addEventListener('mousemove', onMouseMove, false);
}
/* Canvasからマウスが離れた時 */
function onMouseOut() {
    canvas.removeEventListener('mousemove', onMouseMove, false);
}
/* Canvas上でマウスが動いている時 */
function onMouseMove(e) {
    /* マウスが動く度に要素上に乗っているかかどうかをチェック */
    moveActions.updateTargetFlag(e);

    /* 実行する関数には、間引きを噛ませる */
    if (targetFlag) {
        moveActions.throttle(moveActions.over, 50);
    } else {
        moveActions.throttle(moveActions.out, 50);
    }
}

/* mouseMoveで実行する関数 */
var moveActions = {
    timer: null,
    /* targetFlagの更新 */
    updateTargetFlag: function(e) {
        var x = e.clientX - rect.left;
        var y = e.clientY - rect.top;

        /* 最後の50は、該当する要素の半サイズを想定 */
        var a = (x > w / 2 - 50);
        var b = (x < w / 2 + 50);
        var c = (y > h / 2 - 50);
        var d = (y < h / 2 + 50);

        targetFlag = (a && b && c && d); // booleanを代入
    },
    /* 連続イベントの間引き */
    throttle: function(targetFunc, time) {
        var _time = time || 100;
        clearTimeout(this.timer);
        this.timer = setTimeout(function () {
            targetFunc();
        }, _time);
    },
    out: function() {
        drawRect();
    },
    over: function() {
        drawRectIsHover();
    }
};

function drawRect(color) {
    // デフォルトもしくはマウスが要素から離れた時の描画処理
}
function drawRectIsHover() {
    // マウスが要素に乗った時の描画処理
}

canvas.addEventListener('mouseover', onMouseOver, false);
canvas.addEventListener('mouseout', onMouseOut, false);

drawRect();
```


## mouse down / up イベント

- mouse downとclickとの違いは、すぐに発動するかどうか
- down: 押した時
- click: 離れた時

![anim_down-up.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/97a6da25-a7de-2558-bf5d-24bffb831648.gif)

```mousedown-up.js
function onMouseDown() {
    drawOn();
}
function onMouseUp() {
    drawOff();
}
function drawOn() {
    // マウスが押された時の描画処理
}
function drawOff() {
    // デフォルトもしくはマウスが離れた時の描画処理
}

canvas.addEventListener('mousedown', onMouseDown, false);
canvas.addEventListener('mouseup', onMouseUp, false);

drawOff();
```


## mouse move イベント

- mousemoveは連続してイベントが発生するので、重たい処理を行うときには間引きを行った方が良い（即時性を求めない場合）

![anim_move.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/2ae494f8-04e4-c96c-e0d1-9788b936410a.gif)

```mousemove.js
var x = 0;
var y = 0;
var timer;

/*
 * 連続イベント間引き処理
 * @param {obj} targetFunc 処理したい関数
 * @param {num} time 何ミリ秒以内の処理をスキップするか
 */
function throttle(targetFunc, time) {
    var _time = time || 100;
    clearTimeout(this.timer);
    this.timer = setTimeout(function () {
        targetFunc();
    }, _time);
}

function onMouseMove(e) {
    draw(e);
}

function draw(e) {
    throttle(function() {
        // 描画処理
    }, 100);
}

canvas.addEventListener('mousemove', onMouseMove, false);
```


## mouse wheelイベント

- canvas内にマウスが乗っている時に発動する模様
- 特に難しいものは無いのでコード・画像は省略


## key up / downイベント

- canvasに対してこれらのイベント設定するには、canvasにtabindexを付与する必要がある
- documentにイベント設定すれば動作はするが、canvasにfocusが当たってない時も反応してしまう

> 使っているキャプチャソフトだと上手く取れなかったので画像は省略します...

```keyup-down.js
/* キーが押された時 */
function onKeyDown(e) {
    // e.keyCodeによって処理を変えると良いかと
    var str = e.keyCode;
    draw(str);
}
/* キーが離れた時 */
function onKeyUp(e) {
    var str = 'キーから離れました';
    draw(str);
}
function draw(str) {
    // 描画処理
}

canvas.setAttribute('tabindex', 0); // focusしている時のみ、keyDown,up を有効に
canvas.addEventListener('keydown', onKeyDown, false);
canvas.addEventListener('keyup', onKeyUp, false);
```


# おわりに
まだまだ分かっていないことばかりなので、不正確なところがあればコメントや編集リクエストをお願いします