Reputation: 531
I am trying to render a canvas using React. The graphics inside the canvas would also be handled by Reacts render
method.
The current code does render a canvas with the graphics I want. But I am looking for a way to return a canvas with my graphics from within the render
method. The reason I want this, is cause I want this to work in "React Way".
Ideally, I would want my code to be something like this:
return(
<div>
<canvas id='my_canvas_with_graphics_rendered_in_react'></canvas>
</div>
);
First, I want to know if this is even possible. Or if I should look for an alternative.
Maybe Im missing the point of React, if that is the case please let me know. I do hope there is a solution, because from what I've read React touts itself as the V
in MVC
. That is why, if possible, this would be a perfect solution for what I'm trying to do. I would not have to worry about the rendering inside my canvas. I would simply supply the data to the component and React would re-render the changes.
I have marked of in the return
statement where I believe the correct code should go.
The HTML
code:
<div id='wrapper'>
<canvas id='canvas'></canvas>
</div>
The jsx
code:
var MyCanvas = React.createClass({
render:function(){
var line = this.props.lines;
var ctx = this.props.canvas.getContext("2d");
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.moveTo(line[0].x0, line[0].y0);
// .... more code .....
ctx.stroke();
return(
<div>
/* *********************************
??? RETURN MY RENDERED CANVAS
WITH GRAPHIC LINES. ????
This is where I want to return the canvas
********************************* */
</div>
);
}
});
var wrapper = document.getElementById('wrapper');
var canvas = document.getElementById('canvas');
var line1 = { x0: 0, y0: 10, x1: 20, y1: 30 };
var line2 = { x0: 40, y0: 50, x1: 60, y1: 70 };
var myCanvas = <MyCanvas canvas={ canvas } lines={ [line1, line2] } />;
React.render(myCanvas, wrapper);
Hope I made myself clear.
Upvotes: 26
Views: 30474
Reputation: 3288
I ported @matthew answer to use React Hooks. React changes so frequently :)
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
const Canvas = ({ draw }) => {
const canvas = React.createRef();
useEffect(() => {
const ctx = canvas.current.getContext('2d');
draw(ctx);
}, [draw, canvas]);
return <canvas ref={canvas} />;
};
Canvas.propTypes = {
draw: PropTypes.func.isRequired,
};
export default Canvas;
You would use this Canvas by supplying the draw context.
<Canvas
draw={ctx => {
ctx.beginPath();
ctx.arc(95, 50, 40, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
}
/>
Upvotes: 4
Reputation: 1558
I thought I would update this answer using the React.createRef()
because the this.refs
is now legacy. Refs and the DOM
import React from "react";
import * as PropTypes from "prop-types";
class Canvas extends React.Component {
constructor() {
super()
this.canvas = React.createRef()
}
componentDidMount() {
const ctx = this.canvas.current.getContext('2d')
this.props.draw(ctx);
}
render() {
return <canvas ref={this.canvas} />;
}
}
Canvas.propTypes = {
draw: PropTypes.func.isRequired
};
export default Canvas
You would use this Canvas
by supplying the draw context.
<Canvas
draw={ctx => {
ctx.beginPath();
ctx.arc(95, 50, 40, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
}
/>
Upvotes: 8
Reputation: 545
The key is overriding the right React lifecycle methods to do the drawing. The render method creates the canvas DOM element and you can add a refs
callback to set a reference to it, but you have to wait until the component is mounted to draw on the canvas.
Here's an example that uses a callback ref as the React docs recommend. It is designed to render a static (non-animated) graphic component based on props and updates when props change. It also subscribes to the window resize event so that it can be dynamically sized via CSS (such as width: 100%
). This example is based on the code from this Gist.
export default class CanvasComponent extends React.Component {
constructor(props) {
super(props);
this._resizeHandler = () => {
/* Allows CSS to determine size of canvas */
this.canvas.width = this.canvas.clientWidth;
this.canvas.height = this.canvas.clientHeight;
this.clearAndDraw();
}
}
componentDidMount() {
window.addEventListener('resize', this._resizeHandler);
/* Allows CSS to determine size of canvas */
this.canvas.width = this.canvas.clientWidth;
this.canvas.height = this.canvas.clientHeight;
this.clearAndDraw();
}
componentWillUnmount() {
window.removeEventListener('resize', this._resizeHandler);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.secondRect !== prevProps.secondRect) {
this.clearAndDraw();
}
}
clearAndDraw() {
const ctx = this.canvas.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.draw(ctx);
}
}
draw(ctx) {
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);
if (this.props.secondRect) {
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);
}
}
render() {
return (
<canvas ref={canvas => this.canvas = canvas} />
);
}
}
You can set the secondRect
prop from the parent component or set it from state to see the component update when the prop updates. You can extend this idea to build any kind of data-driven canvas rendering, such as a chart or graph.
Upvotes: 9
Reputation: 643
Your only error was that you were not familar with react refs. try this:
class ConnectionChart extends React.Component {
componentDidMount() {
let canvas = ReactDOM.findDOMNode(this.refs.myCanvas);
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fillRect(10, 10, 55, 50);
}
render() {
return (
<div>
<canvas ref="myCanvas" />
</div>
);
}
}
You might have get rid of the ES6 style but you get the idea. Of cause u can paint in other methods too ^^
Upvotes: 21
Reputation: 22933
Instead of using the canvas
in your render
method, you'd do something like this:
var MyCanvas = React.createClass({
componentDidMount: function () {
React.getDOMNode(this).appendChild(this.props.canvas);
},
render: function() {
return <div />;
}
});
You just render an empty div
and wait for React to mount that. When mounted, you append your canvas to the actual DOM node that React created.
The reason for this is that the render
method only returns virtual DOM nodes that React then translates to real DOM nodes. And since you have a real DOM node, you can't convert that to a virtual node. Another reason for is that you should only return nodes from render
that React controls. And since you're managing the canvas from outside of Reacts control, you should use real DOM apis do manage it.
Upvotes: 13