canvasで曲線を描いて動かす

# 概要
曲線を描きながら要素を動かすのに色々試したので自分用にメモ

# 環境
[PixiJS v4](http://www.pixijs.com/)
pixi.jsをよく使うので。。。
[requestAnimationFrame](https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame)で要素を動かす。

# 曲線を描くために
## 基本
今回は、[二次関数](https://ja.wikipedia.org/wiki/%E4%BA%8C%E6%AC%A1%E9%96%A2%E6%95%B0)で波のような動きにする。
学生時代に数学で習ったやつですが、あまり覚えておらず。。。

## 公式
グラフの頂点が（ｐ，ｑ）である２次関数は下記で表されるようです。

```math
y = a ( x - p )^2 + q
```

画面端から画面中央を通り、画面端の対偶に移動させたい。
例えば、画面右上から曲線を描いて画面中心に移動し、曲線を描いて左下に移動していく感じ。

計算は割愛します。

# 右上から左下に移動
```html:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>波のアニメーション</title>

<!-- JS -->
<script src="scripts/lib/jquery-1.11.1.min.js"></script>
<script src="scripts/lib/pixi.min.js"></script>
<script src="scripts/script.js"></script>

</head>

<body>

<div id="word"></div>

</body>
</html>
```
いたって普通だと思います.

### パターン1

```javascript:script.js
$(() => {
    // AnimationFrame Prefix
    const animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.setTimeout;
    const cancelAnimationFrame = window.cancelAnimationFrame || window.mozcancelAnimationFrame || window.webkitcancelAnimationFrame || window.mscancelAnimationFrame;

    const render = () => {            
        // Canvas Size
        const width = window.innerWidth;
        const height = window.innerHeight;
        $('#canvas').attr( 'width', width );
        $('#canvas').attr( 'height', height );

        // Canvas Settings
        const stage = new PIXI.Container();
        const renderer = PIXI.autoDetectRenderer( width, height, { antialias: true, transparent: true } );
        document.getElementById( 'word' ).appendChild( renderer.view );

        const draw = () => {
            // Words Settings
            const themeWord = 'あ';
            const themeStyle = { font: '40pt YuMincho', fill: '#000' };
            const themeTxt = new PIXI.Text( themeWord, themeStyle );
            themeTxt.position.x = width;
            themeTxt.position.y = 0;
            themeTxt.anchor.x = 0.5;
            themeTxt.anchor.y = 0.5;
            themeTxt.alpha = 1;

            // Slope Settings 
            const first_slope = 2 / Math.pow( width, 2 ) * height;
            const secound_slope =  -( 2 * height / Math.pow( width, 2 ) );

            stage.addChild( themeTxt );

            // Word Animation
            const wordAnimate = () => {
                themeTxt.position.x -= 3;

                if( themeTxt.position.x > width / 2 ){
                    const first_exponential = themeTxt.position.x - width;
                    themeTxt.position.y = first_slope * Math.pow( first_exponential, 2 );
                }
                if( themeTxt.position.x < width / 2 ){
                    const secound_exponential = themeTxt.position.x
                    themeTxt.position.y = secound_slope * Math.pow( secound_exponential, 2 ) + height;
                }
                if( themeTxt.position.x <= 0 ){
                    themeTxt.position.x = width;
                }
                renderer.render( stage );
                requestAnimationFrame( wordAnimate );
            }
            wordAnimate();
        }
        draw();
    }

    const init = () => {
        render();
    }

    init();
});
```
ざっくり書きましたが、Slope Settingsの箇所とexponentialを組み合わせてthemeTxt .position.yを変更させています。
画面中央に来たときに二次関数の式を変更させて曲線を描いています。
一度で描ける式が思い浮かばなかったので、数学に詳しい方教えてください。

これで動かしてみると
![1.gif](https://qiita-image-store.s3.amazonaws.com/0/122327/d22772bf-5150-740a-323e-b64093c99672.gif)

こんな動きになりました。

### パターン2
頂点と傾きを変更して計算してみます。

```javascript:script.js
// Slope Settings 
const first_slope = -2 * height / Math.pow( width, 2 );
const secound_slope =  2 * height / Math.pow( width, 2 );

// Word Animation
const wordAnimate = () => {
    themeTxt.position.x -= 3;

    if( themeTxt.position.x > width / 2 ){
        const first_exponential = themeTxt.position.x - ( width / 2 );
       　　themeTxt.position.y = first_slope * Math.pow( first_exponential, 2 ) + ( height / 2 );
    }
    if( themeTxt.position.x < width / 2 ){
        const secound_exponential = themeTxt.position.x - ( width / 2 );
        themeTxt.position.y = secound_slope * Math.pow( secound_exponential, 2 ) + ( height / 2 );
    }
    if( themeTxt.position.x <= 0 ){
        themeTxt.position.x = width;
    }
    renderer.render( stage );
    requestAnimationFrame( wordAnimate );
}
wordAnimate();
```

主な変更箇所だけ記述しました。

こうすると、
![2.gif](https://qiita-image-store.s3.amazonaws.com/0/122327/300a0284-2e30-0b08-ece3-3fbab4ec1e6b.gif)

傾き方が変わりました。

# 左上から右下に移動
始点を変えてみます。

### パターン1

```javascript:script.js
const draw = () => {
    // Words Settings
    const themeWord = 'あ';
    const themeStyle = { font: '40pt YuMincho', fill: '#000' };
    const themeTxt = new PIXI.Text( themeWord, themeStyle );
    themeTxt.position.x = 0;
    themeTxt.position.y = 0;
    themeTxt.anchor.x = 0.5;
    themeTxt.anchor.y = 0.5;
    themeTxt.alpha = 1;

    // Slope Settings 
    const first_slope = 2 * height / Math.pow( width, 2 );
    const secound_slope =  -2 * height / Math.pow( width, 2 );

    stage.addChild( themeTxt );

　　　　　　　　// Word Animation
    const wordAnimate = () => {
        themeTxt.position.x += 3;

        if( themeTxt.position.x < width / 2 ){
            const first_exponential = themeTxt.position.x;
            themeTxt.position.y = first_slope * Math.pow( first_exponential, 2 );
        }
        if( themeTxt.position.x > width / 2 ){
            const secound_exponential = themeTxt.position.x - width;
            themeTxt.position.y = secound_slope * Math.pow( secound_exponential, 2 ) + height;
        }
        if( themeTxt.position.x >= width ){
            themeTxt.position.x = 0;
        }
        renderer.render( stage );
        requestAnimationFrame( wordAnimate );
    }
    wordAnimate();
}
draw();
```

最初の始点の指定やxを増加させているところも変えました。

![3.gif](https://qiita-image-store.s3.amazonaws.com/0/122327/17066426-3b54-e7cd-0211-b5ecad7b5469.gif)

左上からになりました。

### パターン2
今までと同様に

```javascript:script.js
const draw = () => {
    // Words Settings
    const themeWord = 'あ';
    const themeStyle = { font: '40pt YuMincho', fill: '#000' };
    const themeTxt = new PIXI.Text( themeWord, themeStyle );
    themeTxt.position.x = 0;
    themeTxt.position.y = 0;
    themeTxt.anchor.x = 0.5;
    themeTxt.anchor.y = 0.5;
    themeTxt.alpha = 1;

    // Slope Settings 
    const first_slope = -2 * height / Math.pow( width, 2 );
    const secound_slope =  2 * height / Math.pow( width, 2 );

    stage.addChild( themeTxt );

　　　　　　　　// Word Animation
    const wordAnimate = () => {
        themeTxt.position.x += 3;

        if( themeTxt.position.x < width / 2 ){
            const first_exponential = themeTxt.position.x - ( width / 2 );
            themeTxt.position.y = first_slope * Math.pow( first_exponential, 2 ) + height / 2;
        }
        if( themeTxt.position.x > width / 2 ){
            const secound_exponential = themeTxt.position.x - ( width / 2 );
            themeTxt.position.y = secound_slope * Math.pow( secound_exponential, 2 ) + height / 2;
        }
        if( themeTxt.position.x >= width ){
            themeTxt.position.x = 0;
        }
        renderer.render( stage );
        requestAnimationFrame( wordAnimate );
    }
    wordAnimate();
}
draw();
```

![4.gif](https://qiita-image-store.s3.amazonaws.com/0/122327/58c17830-e3e0-1d1e-95a3-a5e711590fcc.gif)

傾き方が変わりました。

# まとめ
二次関数を駆使して曲線を描くことができました。
同じようにすれば下から上に移動するのもできそうです。
どちらかというと数学の問題ですね。
学生のときはピンと来ませんでしたが、こうしてみると少し数学が楽しくなるかもしれません。