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.

Revisions

  1. JobLeonard revised this gist Sep 7, 2016. 1 changed file with 10 additions and 9 deletions.
    19 changes: 10 additions & 9 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -15,14 +15,15 @@ class CanvasEnhancer extends React.Component {
    this.draw = this.draw.bind(this);

    // Attach helper functions to context prototype
    if (CanvasRenderingContext2D.prototype.circle === undefined) {
    CanvasRenderingContext2D.prototype.circle = function (x, y, radius) {
    let prototype = CanvasRenderingContext2D.prototype;
    if (!prototype.circle) {
    prototype.circle = function (x, y, radius) {
    this.moveTo(x + radius, y);
    this.arc(x, y, radius, 0, 2 * Math.PI);
    };
    }
    if (CanvasRenderingContext2D.prototype.textSize === undefined) {
    CanvasRenderingContext2D.prototype.textSize = function (size = 10) {
    if (!prototype.textSize) {
    prototype.textSize = function (size = 10) {
    // will return an array with [ size, font ] as strings
    const fontArgs = this.font.split(' ');
    const font = fontArgs[fontArgs.length - 1];
    @@ -36,19 +37,19 @@ class CanvasEnhancer extends React.Component {
    }
    };
    }
    if (CanvasRenderingContext2D.prototype.textStyle === undefined) {
    CanvasRenderingContext2D.prototype.textStyle = function (fill = 'black', stroke = 'white', lineWidth = 2) {
    if (!prototype.textStyle) {
    prototype.textStyle = function (fill = 'black', stroke = 'white', lineWidth = 2) {
    this.fillStyle = fill;
    this.strokeStyle = stroke;
    this.lineWidth = lineWidth;
    };
    }
    if (CanvasRenderingContext2D.prototype.drawText === undefined) {
    CanvasRenderingContext2D.prototype.drawText = function (text, x, y) {
    if (!prototype.drawText) {
    prototype.drawText = function (text, x, y) {
    this.strokeText(text, x, y);
    this.fillText(text, x, y);
    };
    }
    }
    }

    // Make sure we get a sharp canvas on Retina displays
  2. JobLeonard revised this gist Sep 7, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion canvas.js
    Original file line number Diff line number Diff line change
    @@ -97,7 +97,7 @@ class CanvasEnhancer extends React.Component {
    // and inconsistent across browsers. To make it dependent on
    // the layout of the parent container, we only render it after
    // mounting, after CSS layouting is done.
    let canvas = this.state ? (
    const canvas = this.state ? (
    <canvas
    ref='canvas'
    width={this.state.width}
  3. JobLeonard revised this gist Sep 7, 2016. 1 changed file with 119 additions and 49 deletions.
    168 changes: 119 additions & 49 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -6,56 +6,56 @@ import { debounce } from 'lodash';
    // To determine size/layout, we just use CSS on the div containing
    // the Canvas component (we're using this with flexbox, for example).
    // Expects a "paint" function that takes a "context" to draw on
    // Whenever this component updates it will call this paint function
    // to draw on the canvas. For convenience, pixel dimensions are stored
    // Whenever this component updates it will call this paint function
    // to draw on the canvas. For convenience, pixel dimensions are stored
    // in context.width, context.height and contex.pixelRatio.
    export class Canvas extends React.Component {
    class CanvasEnhancer 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,
    ratio: window.devicePixelRatio || 1,
    resizing: true,
    };

    const resize = () => { this.setState({ resizing: true }); };
    // Because the resize event can fire very often, we
    // add a debouncer to minimise pointless
    // (unmount, resize, remount)-ing of the canvas.
    this.setResize = debounce(resize, 200);
    }

    componentDidMount() {
    this.fitToZoomAndPixelRatio();
    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();
    // Attach helper functions to context prototype
    if (CanvasRenderingContext2D.prototype.circle === undefined) {
    CanvasRenderingContext2D.prototype.circle = function (x, y, radius) {
    this.moveTo(x + radius, y);
    this.arc(x, y, radius, 0, 2 * Math.PI);
    };
    }

    if (!this.state.resizing && !prevProps.loop) {
    this.draw();
    if (CanvasRenderingContext2D.prototype.textSize === undefined) {
    CanvasRenderingContext2D.prototype.textSize = function (size = 10) {
    // will return an array with [ size, font ] as strings
    const fontArgs = this.font.split(' ');
    const font = fontArgs[fontArgs.length - 1];
    switch (typeof size) {
    case 'number':
    this.font = size + 'px ' + font;
    break;
    case 'string':
    this.font = size + font;
    break;
    }
    };
    }
    if (CanvasRenderingContext2D.prototype.textStyle === undefined) {
    CanvasRenderingContext2D.prototype.textStyle = function (fill = 'black', stroke = 'white', lineWidth = 2) {
    this.fillStyle = fill;
    this.strokeStyle = stroke;
    this.lineWidth = lineWidth;
    };
    }
    if (CanvasRenderingContext2D.prototype.drawText === undefined) {
    CanvasRenderingContext2D.prototype.drawText = function (text, x, y) {
    this.strokeText(text, x, y);
    this.fillText(text, x, y);
    };
    }
    }

    // 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() {
    componentDidMount() {
    const view = this.refs.view;
    const ratio = window.devicePixelRatio || 1;
    const width = (view.clientWidth * ratio) | 0;
    @@ -64,11 +64,18 @@ export class Canvas extends React.Component {
    this.setState({ width, height, ratio, resizing });
    }

    componentDidUpdate(prevProps) {
    if (!prevProps.loop) {
    this.draw();
    }
    }


    // Relies on a ref to a DOM element, so only call
    // when canvas element has been rendered!
    draw() {
    const { width, height, ratio, resizing } = this.state;
    if (!resizing) {
    if (this.state) {
    const { width, height, ratio } = this.state;
    const canvas = this.refs.canvas;
    let context = canvas.getContext('2d');
    // store width, height and ratio in context for paint functions
    @@ -78,8 +85,8 @@ export class Canvas extends React.Component {
    // 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? (not entirely sure about this API)
    }
    // is the provided paint function an animation? (not entirely sure about this API)
    if (this.props.loop) {
    window.requestAnimationFrame(this.draw);
    }
    @@ -88,33 +95,96 @@ export class Canvas extends React.Component {
    render() {
    // The way canvas interacts with CSS layouting is a bit buggy
    // and inconsistent across browsers. To make it dependent on
    // the layout of the parent container, we completely remove
    // the node when resizing. After calculations are done,
    // we mount it again at the proper dimensions.
    let canvas = null;
    if (!this.state.resizing) {
    canvas = (<canvas
    // the layout of the parent container, we only render it after
    // mounting, after CSS layouting is done.
    let canvas = this.state ? (
    <canvas
    ref='canvas'
    width={this.state.width}
    height={this.state.height}
    style={{
    width: '100%',
    height: '100%',
    }} />
    );
    }
    ) : null;

    return (
    <div
    ref='view'
    className={this.props.className}
    className={this.props.className ? this.props.className : 'view'}
    style={this.props.style}>
    {canvas}
    </div>
    );
    }
    }

    CanvasEnhancer.propTypes = {
    paint: PropTypes.func.isRequired,
    clear: PropTypes.bool,
    loop: PropTypes.bool,
    className: PropTypes.string,
    style: PropTypes.object,
    };

    // This pattern turns out to be generic enough to
    // warrant its own component
    export class RemountOnResize extends React.Component {
    constructor(props) {
    super(props);
    this.state = { resizing: true };

    const resize = () => { this.setState({ resizing: true }); };
    // Because the resize event can fire very often, we
    // add a debouncer to minimise pointless
    // (unmount, resize, remount)-ing of the child nodes.
    this.setResize = debounce(resize, 500);
    }

    componentDidMount() {
    window.addEventListener('resize', this.setResize);
    this.setState({ resizing: false });
    }

    componentWillUnmount() {
    window.removeEventListener('resize', this.setResize);
    }

    componentDidUpdate(prevProps, prevState) {
    if (!prevState.resizing && this.state.resizing) {
    this.setState({ resizing: false });
    }
    }

    render() {
    return this.state.resizing ? null : this.props.children;
    }
    }


    RemountOnResize.propTypes = {
    className: PropTypes.string,
    style: PropTypes.object,
    children: PropTypes.node,
    };

    export const Canvas = function (props) {
    return (
    <RemountOnResize
    /* Since canvas interferes with CSS layouting,
    we unmount and remount it on resize events */
    >
    <CanvasEnhancer
    paint={props.paint}
    clear={props.clear}
    loop={props.loop}
    className={props.className}
    style={props.style}
    />
    </RemountOnResize>
    );
    };

    Canvas.propTypes = {
    paint: PropTypes.func.isRequired,
    clear: PropTypes.bool,
  4. JobLeonard revised this gist Sep 6, 2016. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ import { debounce } from 'lodash';

    // A simple helper component, wrapping retina logic for canvas and
    // auto-resizing the canvas to fill its parent container.
    // So determine size/layout, just use CSS on the div containing
    // To determine size/layout, we just use CSS on the div containing
    // the Canvas component (we're using this with flexbox, for example).
    // Expects a "paint" function that takes a "context" to draw on
    // Whenever this component updates it will call this paint function
    @@ -86,11 +86,11 @@ export class Canvas extends React.Component {
    }

    render() {
    // Canvas layouting is a bit buggy and inconsistent
    // across browsers. So, to make it dependent on the
    // layout of the parent container, we must completely
    // remove the node when resizing. After calculations
    // are done, we mount it again at the proper dimensions
    // The way canvas interacts with CSS layouting is a bit buggy
    // and inconsistent across browsers. To make it dependent on
    // the layout of the parent container, we completely remove
    // the node when resizing. After calculations are done,
    // we mount it again at the proper dimensions.
    let canvas = null;
    if (!this.state.resizing) {
    canvas = (<canvas
  5. JobLeonard revised this gist Sep 6, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion canvas.js
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ import { debounce } from 'lodash';
    // A simple helper component, wrapping retina logic for canvas and
    // auto-resizing the canvas to fill its parent container.
    // So determine size/layout, just use CSS on the div containing
    // the Canvas component (we're using this with flexbox, fox example).
    // the Canvas component (we're using this with flexbox, for example).
    // Expects a "paint" function that takes a "context" to draw on
    // Whenever this component updates it will call this paint function
    // to draw on the canvas. For convenience, pixel dimensions are stored
  6. JobLeonard revised this gist Sep 6, 2016. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion canvas.js
    Original file line number Diff line number Diff line change
    @@ -106,8 +106,8 @@ export class Canvas extends React.Component {

    return (
    <div
    className='view'
    ref='view'
    className={this.props.className}
    style={this.props.style}>
    {canvas}
    </div>
    @@ -119,5 +119,6 @@ Canvas.propTypes = {
    paint: PropTypes.func.isRequired,
    clear: PropTypes.bool,
    loop: PropTypes.bool,
    className: PropTypes.string,
    style: PropTypes.object,
    };
  7. JobLeonard revised this gist Sep 6, 2016. No changes.
  8. JobLeonard revised this gist Sep 6, 2016. 1 changed file with 31 additions and 18 deletions.
    49 changes: 31 additions & 18 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,12 @@ 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.
    // So determine size/layout, just use CSS on the div containing
    // the Canvas component (we're using this with flexbox, fox example).
    // Expects a "paint" function that takes a "context" to draw on
    // Whenever this component updates it will call this paint function
    // to draw on the canvas. For convenience, pixel dimensions are stored
    // in context.width, context.height and contex.pixelRatio.
    export class Canvas extends React.Component {
    constructor(props) {
    super(props);
    @@ -15,18 +19,19 @@ export class Canvas extends React.Component {
    this.state = {
    width: 1,
    height: 1,
    ratio: window.devicePixelRatio || 1,
    resizing: true,
    };

    const resize = () => { this.setState({ resizing: true }); };
    // Because the resize event can fire very often, we
    // add a debouncer to minimise pointless
    // (unmount, resize, remount)-ing of the canvas.
    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);
    }

    @@ -51,33 +56,41 @@ export class Canvas extends React.Component {
    // 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 ratio = window.devicePixelRatio || 1;
    const width = (view.clientWidth * ratio) | 0;
    const height = (view.clientHeight * ratio) | 0;
    this.setState({ width, height, ratio, resizing: false });
    const resizing = false;
    this.setState({ width, height, ratio, resizing });
    }

    // 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?
    const { width, height, ratio, resizing } = this.state;
    if (!resizing) {
    const canvas = this.refs.canvas;
    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? (not entirely sure about this API)
    }
    if (this.props.loop) {
    window.requestAnimationFrame(this.draw);
    }
    }

    render() {
    // Canvas layouting is a bit buggy and inconsistent
    // across browsers. So, to make it dependent on the
    // layout of the parent container, we must completely
    // remove the node when resizing. After calculations
    // are done, we mount it again at the proper dimensions
    let canvas = null;
    if (!this.state.resizing) {
    canvas = (<canvas
  9. JobLeonard revised this gist Sep 6, 2016. No changes.
  10. JobLeonard revised this gist Sep 6, 2016. 1 changed file with 78 additions and 49 deletions.
    127 changes: 78 additions & 49 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,8 @@
    import React, {PropTypes} from 'react';
    import { debounce } from 'lodash';


    // A simple helper component, wrapping retina logic for canvas.
    // 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 {
    @@ -11,71 +11,100 @@ export class Canvas extends React.Component {

    this.fitToZoomAndPixelRatio = this.fitToZoomAndPixelRatio.bind(this);
    this.draw = this.draw.bind(this);
    }

    // Make sure we get a sharp canvas on Retina displays
    // as well as adjust the canvas on zoomed browsers
    fitToZoomAndPixelRatio() {
    let el = this.refs.canvas;
    if (el) {
    const ratio = window.devicePixelRatio || 1;
    const width = (el.parentNode.clientWidth * ratio) | 0;
    const height = (el.parentNode.clientHeight * ratio) | 0;
    if (width !== el.width || height !== el.height) {
    el.width = width;
    el.height = height;
    let context = el.getContext('2d');
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    context.scale(ratio, ratio);
    context.clearRect(0, 0, el.width, el.height);
    }
    }
    }
    this.state = {
    width: 1,
    height: 1,
    resizing: true,
    };

    draw() {
    let el = this.refs.canvas;
    if (el) {
    this.fitToZoomAndPixelRatio();
    let context = el.getContext('2d');
    this.props.paint(context, el.clientWidth, el.clientHeight);
    }
    const resize = () => { this.setState({ resizing: true }); };
    this.setResize = debounce(resize, 200);
    }

    componentDidMount() {
    this.draw();
    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", debounce(this.draw, 200));
    window.addEventListener('resize', this.setResize);
    }

    componentDidUpdate() {
    this.draw();
    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 style={{
    flex: '1 1 auto',
    margin: 0,
    padding: 0,
    border: 0,
    }}>
    <canvas
    ref='canvas'
    style={{
    display: 'block',
    width: '100%',
    height: '100%' }}
    />
    <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,
    };
  11. JobLeonard revised this gist Aug 14, 2016. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion canvas.js
    Original file line number Diff line number Diff line change
    @@ -66,7 +66,10 @@ export class Canvas extends React.Component {
    }}>
    <canvas
    ref='canvas'
    style={{ display: 'block', width: '100%', height: '100%' }}
    style={{
    display: 'block',
    width: '100%',
    height: '100%' }}
    />
    </div>
    );
  12. JobLeonard revised this gist Aug 14, 2016. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    import React, {PropTypes} from 'react';
    import * as _ from 'lodash';
    import { debounce } from 'lodash';


    // A simple helper component, wrapping retina logic for canvas.
    @@ -49,7 +49,7 @@ export class Canvas extends React.Component {
    // Because the resize event can fire very often, we
    // add a debouncer to minimise pointless
    // resizing/redrawing of the canvas.
    window.addEventListener("resize", _.debounce(this.draw, 200));
    window.addEventListener("resize", debounce(this.draw, 200));
    }

    componentDidUpdate() {
    @@ -75,4 +75,4 @@ export class Canvas extends React.Component {

    Canvas.propTypes = {
    paint: PropTypes.func.isRequired,
    };
    };
  13. JobLeonard revised this gist Aug 14, 2016. 1 changed file with 20 additions and 11 deletions.
    31 changes: 20 additions & 11 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    import React, {PropTypes} from 'react';
    import * as _ from 'lodash';


    // A simple helper component, wrapping retina logic for canvas.
    // Expects a "painter" function that takes a "context" to draw on.
    @@ -16,16 +18,20 @@ export class Canvas extends React.Component {
    fitToZoomAndPixelRatio() {
    let el = this.refs.canvas;
    if (el) {
    let context = el.getContext('2d');
    const ratio = window.devicePixelRatio || 1;
    el.width = el.parentNode.clientWidth * ratio;
    el.height = el.parentNode.clientHeight * ratio;
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    context.scale(ratio, ratio);
    context.clearRect(0, 0, el.width, el.height);
    const width = (el.parentNode.clientWidth * ratio) | 0;
    const height = (el.parentNode.clientHeight * ratio) | 0;
    if (width !== el.width || height !== el.height) {
    el.width = width;
    el.height = height;
    let context = el.getContext('2d');
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    context.scale(ratio, ratio);
    context.clearRect(0, 0, el.width, el.height);
    }
    }
    }

    @@ -40,7 +46,10 @@ export class Canvas extends React.Component {

    componentDidMount() {
    this.draw();
    window.addEventListener("resize", this.draw);
    // Because the resize event can fire very often, we
    // add a debouncer to minimise pointless
    // resizing/redrawing of the canvas.
    window.addEventListener("resize", _.debounce(this.draw, 200));
    }

    componentDidUpdate() {
    @@ -66,4 +75,4 @@ export class Canvas extends React.Component {

    Canvas.propTypes = {
    paint: PropTypes.func.isRequired,
    };
    };
  14. JobLeonard created this gist Aug 9, 2016.
    69 changes: 69 additions & 0 deletions canvas.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    import React, {PropTypes} from 'react';

    // A simple helper component, wrapping retina logic for canvas.
    // 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);
    }

    // Make sure we get a sharp canvas on Retina displays
    // as well as adjust the canvas on zoomed browsers
    fitToZoomAndPixelRatio() {
    let el = this.refs.canvas;
    if (el) {
    let context = el.getContext('2d');
    const ratio = window.devicePixelRatio || 1;
    el.width = el.parentNode.clientWidth * ratio;
    el.height = el.parentNode.clientHeight * ratio;
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    context.scale(ratio, ratio);
    context.clearRect(0, 0, el.width, el.height);
    }
    }

    draw() {
    let el = this.refs.canvas;
    if (el) {
    this.fitToZoomAndPixelRatio();
    let context = el.getContext('2d');
    this.props.paint(context, el.clientWidth, el.clientHeight);
    }
    }

    componentDidMount() {
    this.draw();
    window.addEventListener("resize", this.draw);
    }

    componentDidUpdate() {
    this.draw();
    }

    render() {
    return (
    <div style={{
    flex: '1 1 auto',
    margin: 0,
    padding: 0,
    border: 0,
    }}>
    <canvas
    ref='canvas'
    style={{ display: 'block', width: '100%', height: '100%' }}
    />
    </div>
    );
    }
    }

    Canvas.propTypes = {
    paint: PropTypes.func.isRequired,
    };