How to use React with canvas element?
We have React that helps us to work with massive mutable DOM in a nice immutable functional style. That is awesome.
But I would like to describe a way of using React that helps you to work with mutable object representation of “immutable” <canvas>
element. Yes, it sounds strange. But it works really well.
Motivation
I am using <canvas>
element a lot. I made several complex applications which use <canvas>
as a main view component. Vanilla canvas without any frameworks/libraries can be really difficult for complex applications. So I started to use canvas frameworks a lot. Now I maintain Konva 2d canvas Framework.
Konva
helps a lot, but still now not so good, as I wanted. Also I started to use React in my applications and I really like to use it. So I was thinking how can I use React for drawing graphics on canvas.
Native canvas inside React
It is very easy to access <canvas>
context in React component and draw any graphics:
class CanvasComponent extends React.Component {
componentDidMount() {
this.updateCanvas();
}
updateCanvas() {
const ctx = this.refs.canvas.getContext('2d');
ctx.fillRect(0,0, 100, 100);
}
render() {
return (
<canvas ref="canvas" width={300} height={300}/>
);
}
}
ReactDOM.render(<CanvasComponent/>, document.getElementById('container'));
Demo: http://jsbin.com/xituko/edit?js,output
It works nice for trivial examples. But for complex applications this approach is not so good because we can not use full power of reusable React components.
// “reusable component”
function rect(props) {
const {ctx, x, y, width, height} = props;
ctx.fillRect(x, y, width, height);
}
class CanvasComponent extends React.Component {
componentDidMount() {
this.updateCanvas();
}
componentDidUpdate() {
this.updateCanvas();
}
updateCanvas() {
const ctx = this.refs.canvas.getContext('2d');
ctx.clearRect(0,0, 300, 300);
// draw children “components”
rect({ctx, x: 10, y: 10, width: 50, height: 50});
rect({ctx, x: 110, y: 110, width: 50, height: 50});
}
render() {
return (
<canvas ref="canvas" width={300} height={300}/>
);
}
}
ReactDOM.render(<CanvasComponent/>, document.getElementById('container'));
And what about React’s lifecycle methods (like shouldComponentUpdate etc) and “all representation inside render function”?
Implementation
I really like React approach for building apps. So I made react binding to Konva
objects. And now I can draw canvas stage with different graphic shapes and use it in my React application.
npm install react konva react-konva --save
Then
import React from 'react';
import ReactDOM from 'react-dom';
import {Layer, Rect, Stage, Group} from ‘react-konva’;
class MyRect extends React.Component {
constructor(...args) {
super(...args);
this.state = {
color: 'green'
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
color: Konva.Util.getRandomColor()
});
}
render() {
return (
<Rect
x={10} y={10} width={50} height={50}
fill={this.state.color}
shadowBlur={10}
onClick={this.handleClick}
/>
);
}
}
function App() {
return (
<Stage width={700} height={700}>
<Layer>
<MyRect/>
</Layer>
</Stage>
);
}
ReactDOM.render(<App/>, document.getElementById('container'));
Demo: http://jsbin.com/camene/8/edit?html,js,output
Thanks https://github.com/reactjs/react-art for providing good examples of custom React components.
Comparisons
react-konva vs react-canvas
react-canvas is a completely different react plugin. It allows you to draw DOM-like objects (images, texts) on canvas element in very performant way. It is NOT about drawing graphics, but react-konva is exactly for drawing complex graphics on <canvas>
element from React.
react-konva vs react-art
react-art
allows you to draw graphics on a page. It also supports SVG for output. But it has no support of events of shapes.
react-konva vs vanilla canvas
Performance is one of the main buzz word in react hype.
I made this plugin not for performance reasons. Using vanilla <canvas>
should be more performant because while using react-konva
you have Konva framework on top of <canvas>
and React on top of Konva. But I made this plugin to fight with application complexity. Konva helps here a lot (especially when you need events for objects on canvas, like “click” on shape, it is really hard to do with vanilla canvas). But React helps here much more as it provides very good structure for your codebase and data flow.
Features
react-konva
works on top of Konva
so it has support for all core shapes: Circle
, Rect
, Ellipse
, Line
, Sprite
, Image
, Text
, TextPath
, Star
, Ring
, Arc
, Label
, SVG Path
, RegularPolygon
, Arrow
and you can create your own custom shapes. Also you can use build in drag&drop support, tweening, animations, filters, caching system, desktop and mobile events (mouseenter
, click
, dblclick
, dragstart
, dragmove
, dragend
, tap
, dbltap
, etc) and much more.
// custom shape example
function MyShape() {
return (
<Shape fill=”#00D2FF” draggable
sceneFunc={function (ctx) {
ctx.beginPath();
ctx.moveTo(20, 50);
ctx.lineTo(220, 80);
ctx.quadraticCurveTo(150, 100, 260, 170);
ctx.closePath();
// Konva specific method
ctx.fillStrokeShape(this);
}}
/>
);
}
Demo: http://jsbin.com/gakadi/4/edit?html,js,output
// events example
class MyCircle extends React.Component {
constructor(…args) {
super(…args);
this.state = { isMouseInside: false};
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
handleMouseEnter() {
this.setState({ isMouseInside: true});
}
handleMouseLeave() {
this.setState({ isMouseInside: false});
}
render() {
return (
<Circle
x={100} y={60} radius={50}
fill=”yellow” stroke=”black”
strokeWidth={this.state.isMouseInside ? 5 : 1}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
/>
);
}
}
Demo: http://jsbin.com/tekopu/3/edit?js,output
Links:
Github repo: https://github.com/lavrton/react-konva
Konva framework: http://konvajs.github.io/