📜 ⬆️ ⬇️

About using React with the canvas element

There is such a wonderful React framework that allows you to work with a huge and mutable DOM in a beautifully immotable functional style. This is really cool.

But I would like to tell you about the experience of using React, which allows you to work with a mutable abstraction on an "immutable" canvas element. It sounds weird, but it works fine.

Motivation


I use the <canvas> very often. I have made some fairly complex web applications where canvas is the main element of the data view. Using canvas without any frameworks and libraries can be really difficult in large applications. So I started using frameworks a lot. Now I support the Konva framework (there is a review article https://habrahabr.ru/post/250897/ ).
')
Konva helps a lot, but I want more. I also started using React in my applications, and I really like it. And I thought, how can I use React to draw graphics on canvas?

React + canvas


React + canvas without frameworks


Accessing the canvas context from the React component and drawing something is very simple:

 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

Works great for simple examples. But for a large application, this approach is not very good, since it does not allow creating nested React components:

 // ,     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); //  ""  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 quite convenient Rocket method (such as "shouldComponentUpdate", "componentDidMount", etc.)?
But what about the "full description of the presentation in the render function" approach?

Implementation


I really like the Reactor approach to building an application. So I made the react-konva plugin , which draws on canvas through the Konva framework.

 npm install react konva react-konva --save 

Further

 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/5/edit?html,js,output

Comparisons


react-konva vs react-canvas


react-canvas is a completely different plugin. It allows you to draw "standard DOM objects" (pictures, text) with very high performance. But this plugin does NOT allow you to draw graphics. While react-konva is made just for drawing complex graphics on the canvas using React.

react-konva vs react-art


react-art also allows you to draw graphics and has SVG support, but it does not have event support for graphic shapes.

react-konva vs vanilla canvas


Pro productively speak very often, when mention React. Actually, one of the main reasons for using React is to solve performance problems.

But I did this plugin NOT to solve performance problems. Direct use of canvas should be the most productive way, so using react-konva , we have Konva, which works on top of the canvas, and React, which runs on top of Konva.

But I made a plugin to deal with the complexity of the application. Konva helps a lot (especially when you need events for objects on the canvas, such as "click", without a framework to do it very hard). But React helps even more, as it provides a very good code structure and data movement.

Special features


konva logo

react-konva has support for a large number of basic shapes: Circle, Rect, Ellipse, Line, Sprite, Image, Text, TextPath, Star, Ring, Arc, Label, SVG Path, RegularPolygon, Arrow and you can create your own shapes. There is also built-in support for drag & drop, animation, filters, caching system, desktop and mobile events (mouseenter, click, dblclick, dragstart, dragmove, dragend, tap, dbltap, and so on).

An example of a non-standard figure


 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/2/edit?html,js,output

An example of working with events


 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/1/edit

Links


Github: https://github.com/lavrton/react-konva
Konva framework: http://konvajs.imtqy.com/

Source: https://habr.com/ru/post/276585/


All Articles