1

I have a ReactJS application. I'm dynamically generating SVG images as JSX components. So far, so good -- but now I need to use the SVG as an image source in a Canvas element. This works fine with static SVG files, but how can I get the dynamic SVG into the canvas?

A simplified version appears in the snippet. Two approaches to the drawImage call are shown in the componentDidMount method: creating an unmounted SvgSource, and using a ref to one mounted on the page, but they both fail.

class App extends React.Component {

 // On mount, paint the SVG in the canvas
 componentDidMount(){
   let ctx = this.canvasRef.getContext("2d")
    
    // THIS DOES NOT WORK:
   //ctx.drawImage(new SvgSource({fill: "green"}), 50, 50);
    
    // NOR DOES THIS:
    //ctx.drawImage(this.svgRef, 50, 50);
    
    /* TypeError: Argument 1 of CanvasRenderingContext2D.drawImage could not be converted to any of: HTMLImageElement, SVGImageElement, HTMLCanvasElement, HTMLVideoElement, ImageBitmap. */
  }

  render() {
  return (
  <div>     
    {/* This works, inserting the SvgSource element directly - but that's not what I want. */}
    <SvgSource ref={s => this.svgRef = s} fill="blue" />
    
    {/* I want to use the SVG as an image source in this canvas. */}
   <canvas 
      ref={
      c => this.canvasRef = c 
      /* NB: using older ref syntax because jsFiddle uses React .14 - use CreateRef with version 16 */
      } 
      width={200} 
      height={200} />
  </div>
  );
  }
}

// Our JSX SVG component that provides the custom image to use in the canvas
class SvgSource extends React.Component {
  render() {
    return (
      <svg width={100} height={100} viewBox="0 0 100 100">
        <circle cx={50} cy={50} r={25} fill={this.props.fill || "red"}/>
      </svg>
    );
  }
}

ReactDOM.render(<App />, document.querySelector("#app"))
body {
  background: #20262E;
  padding: 20px;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  min-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

To clarify: I know how to insert ordinary SVG images into canvas elements. The problem is converting a JSX SVG into a valid source for a drawImage on the canvas context, so that this can all be done in React components.

y6nH
  • 478
  • 6
  • 14
  • 1
    Possible duplicate of [Drawing an SVG file on a HTML5 canvas](https://stackoverflow.com/questions/3768565/drawing-an-svg-file-on-a-html5-canvas) and https://stackoverflow.com/questions/27230293/how-to-convert-svg-to-png-using-html5-canvas-javascript-jquery-and-save-on-serve#33227005 and https://stackoverflow.com/questions/54696758/how-do-i-draw-a-javascript-modified-svg-object-on-a-html5-canvas – Kaiido Feb 18 '19 at 14:36
  • Thanks, I hadn't seen those, but they don't really help me. I know how to draw an SVG to canvas. It's specifically the step of converting a JSX object representing an SVG to something the context will accept as an image that's eluding me. – y6nH Feb 18 '19 at 14:48
  • 1
    You just need to convert this jsx object to markup. The linked answers give you the DOM to markup to uri to image parts. And while I'm not a react ninja, it seems you already have that markup at hand. – Kaiido Feb 18 '19 at 14:56

1 Answers1

0

This is not a very graceful way to do it, but it's what I ended up with:

class SvgSource extends React.Component {
    generateSvg() {
        return "data:image/svg+xml;base64," + 
            btoa(`<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="25" fill="${this.props.fill}" /></svg>`);
    }

    render() {
        return (<img src={this.generateSvg()} />);
    }
}

Instead of making the SVG a JSX object, I just construct it as a string, encoding to base64 for use as a data URL. I can then return an <img> JSX element from the render() function to use the image directly in the page. When I need to use it in a canvas, I can call new SvgSource({fill: "green"}).generateSvg() directly, using the returned string in a plain (non-JSX) <img> element's src attribute.

I could find no better way to get the SVG markup rendered without actually putting it on the page.

y6nH
  • 478
  • 6
  • 14