21

I made a color picker with React and Canvas. Currently the components are rendered in React and canvas is done with vanilla javascript. I'd like to two to mesh more, so I want the click events to be handled with React.

For example, this

colorStrip.addEventListener("click", click, false);

function click(e) {
  x = e.offsetX;
  y = e.offsetY;
  var imageData = context.getImageData(x, y, 1, 1).data;
  rgbaColor = 'rgba(' + imageData[0] + ',' + imageData[1] + ',' + imageData[2] + ',1)';
  fillGradient();
}

I would hope would be able to translate to this

var ColorPicker = React.createClass({
  colorStripClick: function() {
    //handle click events here
  },
  render: function() {
    var styles = {
      opacity: this.props.isVisible ? '1' : '0'
    };
    return(
      <div id="color-picker" style={styles}>
        <canvas id="color-block" height="150" width="150"></canvas>
        <canvas id="color-strip" height="150" width="30" onClick={this.colorStripClick}></canvas>
      </div>
    );
  }
});

But that doesn't work because I don't know how to access context. How can I get access to the canvas properties with React? Is there a way to access it before the click?

UPDATE

I used David's answer but I was getting errors by putting a function in ref so I did ref="canvasBlock" and ref="canvasStrip" instead and then assigned the context in componentDidMount

cocoa
  • 3,806
  • 7
  • 29
  • 56
  • I made a similar project https://github.com/gibbok/react-color-picker-palette you can look on how to access your context in this example for function getDrawingContex() {: https://github.com/gibbok/react-color-picker-palette/blob/master/ColorPickerPalette.jsx – GibboK Jul 06 '16 at 06:42
  • in the function by David you have to add "if (c == null)" I guess because you get the element reference just once on mount, not on rerenders. – Aus Jul 27 '16 at 08:59

6 Answers6

34

In accordance to React16 You can use React.createRef()

class ColorPicker extends React.Component {
constructor(props) {
   super(props);

   this.colorPickerRef = React.createRef();
}

componentDidMount() {
   this.context = this.colorPickerRef.current.getContext('2d');
}

render() {
   return (
      <canvas ref={this.colorPickerRef} />
   )
}
}
Alex S
  • 481
  • 4
  • 6
20

You can add a ref function attribute on the canvas element:

<canvas id="color-strip" ref={(c) => this.context = c.getContext('2d')} height="...

Then you’ll have access to the context through this.context:

colorStripClick: function() {
    var imageData = this.context.getImageData(x, y, 1, 1).data
}

You can also use the event object to access to DOM node as already pointed out, but this way you’ll have access from anywhere, not just event handlers.

David Hellsing
  • 106,495
  • 44
  • 176
  • 212
18

Here is the answer with react hooks:

import { useEffect, useRef } from 'react'

export function Canvas() {
  const ref = useRef()

  useEffect(() => {
    if (ref.current) {
      const canvas = ref.current.getContext('2d')
      // do something here with the canvas
    }
  }, [])

  return <canvas ref={ref} />
}

TypeScript

And if you're using TypeScript:

import { useEffect, useRef } from 'react'

export function Canvas() {
  const ref = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    if (ref.current) {
      const canvas = ref.current.getContext('2d')
      // do something here with the canvas
    }
  }, [])

  return <canvas ref={ref} />
}

On Mount

If you only want to do something with the canvas instance once (when the component "mounts") you're probably better off doing it with useCallback. This way, your effect won't run twice in React Strict Mode.

import { useEffect, useCallback } from 'react'

export function Canvas() {
  const refCallback = useCallback((canvas: HTMLCanvasElement) => {
    // do something here with the canvas
  }, [])
  return <canvas ref={refCallback} />
}
Fernando Rojo
  • 2,633
  • 19
  • 17
1

It should just be accessing the target of the click

colorStripClick: function(e) {
  var ctx = e.target.getContext('2d')
}
PhilVarg
  • 4,762
  • 2
  • 19
  • 37
1

Like this

colorStripClick: function (e) {
    var context = e.currentTarget.getContext('2d');
    // your code
}

Example

Oleksandr T.
  • 76,493
  • 17
  • 173
  • 144
1

Here's the React way to remove a canvas from your component:

const canvas = ReactDOM.findDOMNode(this.refs.canvas); const context = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height);

As long as you can target the DOM Node the react way, you can pretty much access the Canvas Rendering API.

Tamizaan
  • 11
  • 2