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

More than 1 year has passed since last update.

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と要素を組み合わせて使う