LoginSignup
4
2

More than 5 years have passed since last update.

React で canvas をアニメーションさせる

Last updated at Posted at 2018-06-02

React で canvas を操作するのに少し苦労したので、メモとして。

code

canvas_animation.jsx
import React from 'react'
import ReactDom from 'react-dom'

class KeyVisual extends React.Component {
    constructor(){
        super();
        this.canvas = null;
        this.state = {
            width : window.innerWidth,
            height : window.innerHeight,
            color : 360,
        }

        this.animationIds = null;
    }

    handleResize( event ){
        this.setState( { width: window.innerWidth, height: window.innerHeight } );
        this.initCannvas();
    }

    componentDidMount(){
        window.addEventListener('resize', () => { this.handleResize() } );
        this.initCannvas();
    }

    componentWillUnmount() {
        window.removeEventListener('resize', () => { this.handleResize() });
    }

    render(){
        let styles = {
            height: this.state.height
        }

        return(
            <div className="main" style={ styles } >
            <canvas
            ref={ (self) => { this.canvas = self; } }
            width={ this.state.width }
            height={ this.state.height }
            >
            </canvas>
            </div>
        )
    }

    initCannvas(){
        let context = this.canvas.getContext('2d');

        this.COLOR_TEMPLATE = 'hsla(%hue%, 100%, %light%, %alpha%)';


        if( this.animationIds !== null ){
             // リサイズ時には animation を一回とめる
            cancelAnimationFrame( this.animationIds );
        }

        this.renderCanvas( context );
    }

    renderCanvas( context ){
         // アロー関数で this を bind しておく
        this.animationIds = requestAnimationFrame( () => { this.renderCanvas( context ) } );

        context.clearRect( 0, 0, this.state.width, this.state.height );

        let radius = this.state.width / 2;

        context.save();
        context.beginPath();
        var gradient = context.createRadialGradient( 0, 0, 0, 0, 0, radius);
        gradient.addColorStop(0,   this.COLOR_TEMPLATE.replace(/%hue%/, this.state.color-30).replace(/%light%/, '90%').replace(/%alpha%/, '0.6'));
        gradient.addColorStop(0.8, this.COLOR_TEMPLATE.replace(/%hue%/, this.state.color-20).replace(/%light%/, '70%').replace(/%alpha%/, '0.4'));
        gradient.addColorStop(1,   this.COLOR_TEMPLATE.replace(/%hue%/, this.state.color).replace(/%light%/, '50%').replace(/%alpha%/, '0.1'));

        context.fillStyle = gradient;
        context.arc( this.state.width / 2, this.state.height/2, radius, 0, Math.PI * 2 , false);
        context.fill();
        context.restore();

        //次のフレームでの hue を決定する。
        let nextColor = this.state.color - Math.random();
        if( nextColor < 0 ){ nextColor = 360 };
        this.setState( { color: nextColor  } );
    }
}

export default KeyVisual;

内容

まず、 callback ref で dom 要素を一旦保存しておくというのがポイントのようです。
この ref についてはなかなか理解できていない部分があるので、もう少しちゃんと理解する必要がありそうです。

canvas を 変数に保持できたので、あとは普通に context をとってきて、
requestAnimationFrame でループさせるだけ... とおもったのですが、
requestAnimationFrame の引数に入れるときは アロー関数にして
this を bind しておかないとうまく動かないので注意です。

あとは、リサイズに応じて canvas サイズを変えたり、
requestAnimationFrame を止めたりすれば アニメーション完成。
サンプルでは、画面の中央に円が書かれて、色が変わるようなアニメーションにしました。

参考

Refs and the Dom
React/Reduxと要素を組み合わせて使う

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2