React Canvas Component
This is just a quick post to show some typescript code that implements a React canvas component. It’s a bit opionated in that I am using an Rx stream for the image data being displayed. It’s a stateless/dumb component that displays low level image data. Hit me up in the comments if you have any questions.
Typescript React Canvas Component
- Updated on 10 May 2017 to a more efficient implementation *
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import * as React from 'react';
import * as Rx from 'rx';
import * as ReactDOM from 'react-dom';
export class ReactiveCanvasImage {
constructor(
public width: number, //assuming stride to be equal width
public height: number,
public pixels: number[]) { } //pixels is an array of bytes
}
// Defines the properties of the ReactiveCanvas
// It is important to have width and height pre-defined for a
// high frame rate. If width and height are reset per image in
// the imageStream then frame rate suffers.
// height: in pixels
// width: in pixels
// imageMIMEType: e.g. 'image/jpeg'
// imageStream: and observable of ReactiveCanvasImage to display
export interface IReactiveCanvasProps {
height: number;
width: number;
imageMIMEType: string;
imageStream: Rx.Observable<ReactiveCanvasImage>;
}
export class ReactiveCanvas extends React.Component<IReactiveCanvasProps, undefined> {
private disp: Rx.IDisposable;
private ctx: CanvasRenderingContext2D;
private canvas: HTMLCanvasElement;
refs: {
canvas: HTMLCanvasElement;
};
componentDidMount() {
try {
this.canvas = (ReactDOM.findDOMNode(this.refs.canvas) as HTMLCanvasElement);
this.ctx = this.canvas.getContext('2d')!; //trailing bang removes null or undefined from the expression
this.canvas.width = this.props.height;
this.canvas.height = this.props.width;
this.disp =
this.props.imageStream
.throttle(20) //this is necessary since node/js is single threaded. Without it there is no time for anything else to process.
.subscribe(i => {
this.updateCanvas(i);
});
} catch (error) {
console.log(error);
}
}
componentWillUnmount() {
this.disp.dispose();
}
shouldComponentUpdate() {
return false;
}
render() {
return (
<div className='center-children'>
<canvas ref='canvas' className='render-canvas'></canvas>
</div>
);
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
// updateCanvas is called for each frame to be rendered to the canvas.
updateCanvas(reactiveImage: ReactiveCanvasImage) {
try {
let img = new Image();
let blob = new Blob([reactiveImage.pixels], { type: this.props.imageMIMEType });
let url = URL.createObjectURL(blob);
img.onload = () => {
let startTime = Date.now();
this.clear();
this.ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
console.log('Time to draw/load img: ' + (Date.now() - startTime));
};
img.src = url;
} catch (error) {
console.log(error);
}
}
}
You will need to run these npm installs on your project to add the dependencies. If you don’t have typescript installed you will need that too.
npm install react react-dom rx --save
npm install @types/react @types/react-dom @types/rx --save-dev
Written on April 20, 2017