-->

React SVG component as background-image to div

2020-07-06 06:04发布

问题:

I am trying to get styled-components to take an SVG that is a react component and set it as a background-image but I get the error:

TypeError: Cannot convert a Symbol value to a string

SVG component code:

import React from "react";

const testSVG = props => {
  return (
    <svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 193.3 129.7">
      <g>
        <g>
          <g>
            <path
              fill="#434343"
              class="st0"
              d="M162.4,116c-4.6,7.5-15.6,13.6-24.4,13.6H16c-8.8,0-12.3-6.2-7.7-13.7c0,0,4.6-7.7,11.1-18.4
                c4.4-7.4,8.9-14.7,13.3-22.1c1.1-1.8,6.1-7.7,6.1-10.1c0-7.3-15-24.9-18.5-30.7C13.5,23.6,8.3,14.9,8.3,14.9
                C3.7,7.4,7.2,1.2,16,1.2c0,0,65.9,0,106.8,0c10,0,25.9-4.5,33.5,3.8c8.7,9.3,15.5,25.4,22.5,36.8c2.6,4.2,14.5,18.3,14.5,23.8
                c-0.1,7.6-16,26.1-19.6,32C167.1,108.3,162.4,116,162.4,116z"
            />
          </g>
        </g>
      </g>
    </svg>
  );
};

export default testSVG;

Container component code:

import React, { Component } from "react";
import StepSVG from "../../components/UI/SVGs/Test";

class StepBar extends Component {
  render() {
    const { steps } = this.props;

    return (
      <div>
        {steps
          ? steps.map(step => {
              return (
                <Wrap key={step._id} bg={StepSVG}>
                  <P>
                    {" "}
                    <Link to={"/step/" + step._id}>{step.title} </Link>
                  </P>
                </Wrap>
              );
            })
          : null}
      </div>
    );
  }
}

export default StepBar;

const Wrap = styled.div`
  padding: 30px 30px 0 0;
  display: inline-block;
  background-image: url(${props => props.bg});
`;

I am using create-react-app out of the box.

I also tried without using styled components and used inline styles in place to no success.

Is it possible to use an SVG as a background-image in this context with the SVG being a component?

回答1:

Unfortunately, what you're trying to achieve is not possible. You'll have to keep your SVG as an actual .svg file (and link to that), or do some CSS trickery to place SVG component behind your foreground-component.

In other words:

<!-- step.svg -->
<svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 193.3 129.7">
  <!-- copy paste your svg here -->
</svg>

And in your component:

import stepSvgUrl from "../../components/UI/SVGs/step.
// ...
<Wrap key={step._id} bg={stepSvgUrl}>

When you import an SVG file like that, create-react-app applies a Webpack loader that includes the generated URL to the SVG you're importing - which is fine to pass into the background-image css property.

Hope this helps!



回答2:

For React SVG background images, I find this works best:

// MyComponent.js

import mySvg from './mySvg.svg';

...

function MyComponent() {
  return (
    <div
       className="someClassName"
       style={{ backgroundImage: `url(${mySvg})` }}
    > 

... etc

The path to the SVG file will change when webpack bundles, so by using the template string you can keep things linked up.

In the CSS class you can do whatever styling you like.



回答3:

Actually, there is a way to achieve what you want, without the SVG beeing a component:

Here is the updated StepBar component:

import React, { Component } from "react";
import StepSVG from "../../components/UI/SVGs/test.svg";
import encodeSVG from '../../lib/encodeSVG';

class StepBar extends Component {
  render() {
    const { steps } = this.props;

    return (
      <div>
        {steps
          ? steps.map(step => {
              return (
                <Wrap key={step._id} bg={StepSVG}>
                  <P>
                    {" "}
                    <Link to={"/step/" + step._id}>{step.title} </Link>
                  </P>
                </Wrap>
              );
            })
          : null}
      </div>
    );
  }
}

export default StepBar;

const Wrap = styled.div`
  padding: 30px 30px 0 0;
  display: inline-block;
  background-image: ${encodeSVG(bg, '#FF9900')};
`;

Here is encodeSVG lib file:

var symbols = /[\r\n"%#()<>?\[\\\]^`{|}]/g;

function addNameSpace(data) {
  if (data.indexOf('http://www.w3.org/2000/svg') < 0) {
    data = data.replace(/<svg/g, "<svg xmlns='http://www.w3.org/2000/svg'");
  }

  return data;
}

function encodeSVG(data) {
  // Use single quotes instead of double to avoid encoding.
  if (data.indexOf('"') >= 0) {
    data = data.replace(/"/g, "'");
  }

  data = data.replace(/>\s{1,}</g, '><');
  data = data.replace(/\s{2,}/g, ' ');

  return data.replace(symbols, encodeURIComponent);
}

var encode = function(svg, fill) {
  if (fill) {
    svg = svg.replace(/<svg/g, `<svg fill="${fill}"`);
  }
  var namespaced = addNameSpace(svg);
  var dimensionsRemoved = namespaced
    .replace(/height="\w*" /g, '')
    .replace(/width="\w*" /g, '')
    .replace(/height='\w*' /g, '')
    .replace(/width='\w*' /g, '');
  var encoded = encodeSVG(dimensionsRemoved);

  var header = 'data:image/svg+xml,';
  var dataUrl = header + encoded;  

  return `url("${dataUrl}")`;
};

You import the svg, and use a string loader for svgs with your preferred build system (for example WebPack), pass that svg to encodeSVG, et voila! :)



回答4:

url(`data:image/svg+xml;utf8,${svgTxt}`)

;or

url(`data:image/svg+xml;base64,${btoa(svgTxt)}`)

that's it.



回答5:

I ended up using user11011301's solution in combination with raw-loader to load the SVG content. Not the cleanest solution I'm sure, but I couldn't get it to work with other webpack loaders.

import searchIcon from '../../images/search.svg';

<input style={{backgroundImage: `url(data:image/svg+xml;base64,${btoa(searchIcon)})`}}/>


回答6:

You should probably convert it to string (as the error points out). Obviously you cannot use a React component as a background image because the react component is actually a javascript object (JSX converts to JS, then to HTML). So what you can do/try is use, ReactDOMServer (https://reactjs.org/docs/react-dom-server.html).

You could try something with:

ReactDOMServer.renderToString(StepSVG)

or

ReactDOMServer.renderToStaticMarkup(StepSVG)

I haven't tested if this actually works but worth a shot for you I guess :)