Skip to content

Instantly share code, notes, and snippets.

@JobLeonard
Last active July 29, 2023 01:44
Show Gist options
  • Select an option

  • Save JobLeonard/987731e86b473d42cd1885e70eed616a to your computer and use it in GitHub Desktop.

Select an option

Save JobLeonard/987731e86b473d42cd1885e70eed616a to your computer and use it in GitHub Desktop.
A react component that wraps and autosizes a canvas element
import React, {PropTypes} from 'react';
import { debounce } from 'lodash';
// A simple helper component, wrapping retina logic for canvas and
// auto-resizing the canvas to fill its parent container.
// Expects a "painter" function that takes a "context" to draw on.
// This will draw on the canvas whenever the component updates.
export class Canvas extends React.Component {
constructor(props) {
super(props);
this.fitToZoomAndPixelRatio = this.fitToZoomAndPixelRatio.bind(this);
this.draw = this.draw.bind(this);
this.state = {
width: 1,
height: 1,
resizing: true,
};
const resize = () => { this.setState({ resizing: true }); };
this.setResize = debounce(resize, 200);
}
componentDidMount() {
this.fitToZoomAndPixelRatio();
// Because the resize event can fire very often, we
// add a debouncer to minimise pointless
// resizing/redrawing of the canvas.
window.addEventListener('resize', this.setResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.setResize);
}
componentDidUpdate(prevProps, prevState) {
// if resizing was not true both before the update,
// but is now, then the canvas has been unmounted
if (!prevState.resizing && this.state.resizing) {
this.fitToZoomAndPixelRatio();
}
if (!this.state.resizing && !prevProps.loop) {
this.draw();
}
}
// Make sure we get a sharp canvas on Retina displays
// as well as adjust the canvas on zoomed browsers
// Does NOT scale; painter functions decide how to handle
// window.devicePixelRatio on a case-by-case basis
fitToZoomAndPixelRatio() {
const ratio = window.devicePixelRatio || 1;
const view = this.refs.view;
const width = (view.clientWidth * ratio) | 0;
const height = (view.clientHeight * ratio) | 0;
this.setState({ width, height, ratio, resizing: false });
}
// Relies on a ref to a DOM element, so only call
// when canvas element has been rendered!
draw() {
let canvas = this.refs.canvas;
const { width, height, ratio } = this.state;
let context = canvas.getContext('2d');
// store width, height and ratio in context for paint functions
context.width = width;
context.height = height;
context.pixelRatio = ratio;
// should we clear the canvas every redraw?
if (this.props.clear) { context.clearRect(0, 0, canvas.width, canvas.height); }
this.props.paint(context);
// is the provided paint function an animation?
if (this.props.loop) {
window.requestAnimationFrame(this.draw);
}
}
render() {
let canvas = null;
if (!this.state.resizing) {
canvas = (<canvas
ref='canvas'
width={this.state.width}
height={this.state.height}
style={{
width: '100%',
height: '100%',
}} />
);
}
return (
<div
className='view'
ref='view'
style={this.props.style}>
{canvas}
</div>
);
}
}
Canvas.propTypes = {
paint: PropTypes.func.isRequired,
clear: PropTypes.bool,
loop: PropTypes.bool,
style: PropTypes.object,
};
@dstroot
Copy link
Copy Markdown

dstroot commented Oct 12, 2018

Will this work for drawing an image on the canvas? e.g. context.drawImage(base_image, 0, 0);

I don't understand why the prototype had to be extended with circle, testSize, etc. Does it need a drawImage?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment