将React SVG组件作为div的背景图片

38

我正在尝试使用styled-components将一个作为React组件的SVG设置为背景图像,但是我遇到了错误:

TypeError:无法将Symbol值转换为字符串

SVG组件代码:

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;

容器组件代码:

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});
`;

我正在使用开箱即用的create-react-app。

我也尝试过不使用styled components,而是使用内联样式来替代,但没有成功。

在这种情况下,是否可能将SVG作为背景图像,并且SVG作为组件使用?

7个回答

40

对于 React SVG 背景图片,我发现以下方法效果最好:

// MyComponent.js

import mySvg from './mySvg.svg';

...

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

... etc

使用模板字符串,可以确保在webpack打包时SVG文件的路径得到正确链接,因为路径会发生改变。

在CSS类中,您可以进行任何所需的样式设置。


12
问题是关于将SVG作为React组件创建,而不是从文件中加载普通的SVG。 - johannes_lalala
3
我认为更好的做法是澄清将svg移动到.svg文件中会使其成为有效的解决方案。由于svg仅作为背景使用,这样做没有问题。 - Martin Watson

12

很遗憾,您想实现的内容是不可能的。您需要将SVG文件作为实际的.svg文件保存(并链接到该文件),或者进行一些CSS技巧来将SVG组件放置在前景组件后面。

换句话说:

<!-- 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>

并且在您的组件中:

import stepSvgUrl from "../../components/UI/SVGs/step.
// ...
<Wrap key={step._id} bg={stepSvgUrl}>
当您像这样导入SVG文件时,create-react-app会应用Webpack加载器,其中包括您导入的SVG生成的URL - 这很好地传递到background-image css属性中。
希望对您有所帮助!

1
谢谢。这是我的原始方法,但我正在尝试动态地渲染颜色。我猜我只需要为此方法中的每种颜色创建N个静态SVG? - user8467470
1
这是一种方法,但你仍然可以使用原始的方法。在这种情况下,您需要使用CSS将SVG图层放置在内容下方(z-index属性将有所帮助)。 - Kris Selbekk

6

最终我使用了 user11011301 的解决方案,结合 raw-loader 来加载 SVG 内容。虽然并不是最干净的解决方案,但我无法让它与其他 webpack 加载器正常工作。

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

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

4

实际上,有一种方法可以实现你想要的效果,而不需要将SVG作为组件:

这里是更新后的StepBar组件:

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')};
`;

这里是encodeSVG库文件:

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}")`;
};

您需要导入SVG,并使用字符串加载器来处理SVG,可以选择您喜欢的构建系统(例如Webpack),将该SVG传递给encodeSVG,然后就完成了! :)

你的示例中bg变量没有被初始化。 - Navidot

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

或者

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

就这些了。


2
这对我有用:
import * as ReactDOMServer from "react-dom/server";

// svg component
function MySVG({}: Props) {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
      // ...
    </svg>
  );
}

// magic
const svgString = encodeURIComponent(
  ReactDOMServer.renderToStaticMarkup(<MySVG />)
);

// renders
<div style={{ backgroundImage: `url("data:image/svg+xml,${svgString}")` }}>
</div>;

查看更多:沙盒演示


1

你应该将它转换为字符串(正如错误所指出的那样)。显然,你不能将React组件用作背景图片,因为React组件实际上是一个JavaScript对象(JSX转换为JS,然后转换为HTML)。所以你可以尝试使用ReactDOMServer(https://reactjs.org/docs/react-dom-server.html)。

你可以尝试以下代码:

ReactDOMServer.renderToString(StepSVG)

或者

ReactDOMServer.renderToStaticMarkup(StepSVG)

我还没有测试过这个是否有效,但我猜对你来说试一下也无妨 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接