美文网首页
在React-Native中使用SVG图片

在React-Native中使用SVG图片

作者: AizawaSayo | 来源:发表于2021-01-04 16:33 被阅读0次

    React-Native是不能直接引入SVG图片的,但是我们可以借助react-native-svgreact-native-svg-uri这两个库来实现,下面是实现过程:

    先安装react-native-svg:

    yarn add react-native-svg

    准备好要用的svg图片

    我把svg都放在项目/src/assets/icons下,可以根据需要自定义这个文件夹的名字,只要在下面用到的地方同步修改即可。

    icons同级目录/src/assets/下新增一个getSvg.js(名字随意),作用是将svg图片文件夹转成一个js文件,内容如下:

    // 导入node的文件模块
    var fs = require('fs');
    var path = require('path');
    // 定义想要处理的svg文件夹路径,根据自己定义的文件夹名修改
    const svgDir = path.resolve(__dirname, './icons');
    
    // 读取单个文件
    function readfile(filename) {
      return new Promise((resolve, reject) => {
        fs.readFile(path.join(svgDir, filename), 'utf8', function(err, data) {
          console.log(data.replace(/<\?xml.*?\?>|<\!--.*?-->|<!DOCTYPE.*?>/g, ''));
          if (err) reject(err);
          resolve({
            [filename.slice(0, filename.lastIndexOf('.'))]: data,
          });
        });
      });
    }
    
    // 读取SVG文件夹下所有svg
    function readSvgs() {
      return new Promise((resolve, reject) => {
       fs.readdir(svgDir, function(err, files) {
         if (err) reject(err);
         Promise.all(files.map(filename => readfile(filename)))
          .then(data => resolve(data))
          .catch(err => reject(err));
       });
      });
    }
    
    // 在当前的目录下生成svgs.js
    readSvgs().then(data => {
      let svgFile = 'export default ' + JSON.stringify(Object.assign.apply(this, data));
      fs.writeFile(path.resolve(__dirname, './icons.js'), svgFile, function(err) {
        if(err) throw new Error(err);
      })
    }).catch(err => {
        throw new Error(err);
      });
    
    

    切换到项目里getSvg.js所在的文件夹,执行转换

    cd src/assets
    node getSvg.js
    
    转换完毕后assets文件夹里的内容

    去下载react-native-svg-uri的源码,github上的地址是https://github.com/vault-development/react-native-svg-uri

    注意,我们不必安装这个库,只要拿到下图中的两个js,修改以下为我们所用即可。


    1. index.js进行改造,因为后面要放在/src/utils文件夹下,为避免混淆我把名字改成了svgUri.js
    // svgUri.js,原index.js
    import React, {Component} from 'react';
    import {View} from 'react-native';
    import PropTypes from 'prop-types';
    import xmldom from 'xmldom';
    import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
    
    import Svg, {
      Circle,
      Ellipse,
      G,
      LinearGradient,
      RadialGradient,
      Line,
      Path,
      Polygon,
      Polyline,
      Rect,
      Text,
      TSpan,
      Defs,
      Stop,
    } from 'react-native-svg';
    
    import * as utils from '@utils/svgUriUtils';
    
    const ACCEPTED_SVG_ELEMENTS = [
      'svg',
      'g',
      'circle',
      'path',
      'rect',
      'defs',
      'line',
      'linearGradient',
      'radialGradient',
      'stop',
      'ellipse',
      'polygon',
      'polyline',
      'text',
      'tspan',
    ];
    
    // Attributes from SVG elements that are mapped directly.
    const SVG_ATTS = ['viewBox', 'width', 'height'];
    const G_ATTS = ['id'];
    
    const CIRCLE_ATTS = ['cx', 'cy', 'r'];
    const PATH_ATTS = ['d'];
    const RECT_ATTS = ['width', 'height'];
    const LINE_ATTS = ['x1', 'y1', 'x2', 'y2'];
    const LINEARG_ATTS = LINE_ATTS.concat(['id', 'gradientUnits']);
    const RADIALG_ATTS = CIRCLE_ATTS.concat(['id', 'gradientUnits']);
    const STOP_ATTS = ['offset'];
    const ELLIPSE_ATTS = ['cx', 'cy', 'rx', 'ry'];
    
    const TEXT_ATTS = ['fontFamily', 'fontSize', 'fontWeight', 'textAnchor'];
    
    const POLYGON_ATTS = ['points'];
    const POLYLINE_ATTS = ['points'];
    
    const COMMON_ATTS = [
      'fill',
      'fillOpacity',
      'stroke',
      'strokeWidth',
      'strokeOpacity',
      'opacity',
      'strokeLinecap',
      'strokeLinejoin',
      'strokeDasharray',
      'strokeDashoffset',
      'x',
      'y',
      'rotate',
      'scale',
      'origin',
      'originX',
      'originY',
      'transform',
      'clipPath',
    ];
    
    let ind = 0;
    
    function fixYPosition(y, node) {
      if (node.attributes) {
        const fontSizeAttr = Object.keys(node.attributes).find(
          (a) => node.attributes[a].name === 'font-size',
        );
        if (fontSizeAttr) {
          return (
            '' + (parseFloat(y) - parseFloat(node.attributes[fontSizeAttr].value))
          );
        }
      }
      if (!node.parentNode) {
        return y;
      }
      return fixYPosition(y, node.parentNode);
    }
    
    class SvgUri extends Component {
      state = {
        fill: this.props.fill,
        svgXmlData: this.props.svgXmlData,
        createSVGElement: this.createSVGElement.bind(this),
        obtainComponentAtts: this.obtainComponentAtts.bind(this),
        inspectNode: this.inspectNode.bind(this),
        fetchSVGData: this.fetchSVGData.bind(this),
        isComponentMounted: false,
        // Gets the image data from an URL or a static file
      };
    
      componentDidMount() {
        if (this.props.source) {
          console.log(this.props);
          const source = resolveAssetSource(this.props.source) || {};
          this.fetchSVGData(source.uri);
        }
        this.setState({
          isComponentMounted: true,
        });
        // this.isComponentMounted = true;
      }
      UNSAFE_componentWillReceiveProps(nextProps) {
        if (nextProps.source) {
          const source = resolveAssetSource(nextProps.source) || {};
          const oldSource = resolveAssetSource(this.props.source) || {};
          if (source.uri !== oldSource.uri) {
            this.fetchSVGData(source.uri);
          }
        }
    
        if (nextProps.svgXmlData !== this.props.svgXmlData) {
          this.setState({svgXmlData: nextProps.svgXmlData});
        }
    
        if (nextProps.fill !== this.props.fill) {
          this.setState({fill: nextProps.fill});
        }
      }
    
      componentWillUnmount() {
        this.isComponentMounted = false;
      }
    
      async fetchSVGData(uri) {
        let responseXML = null,
          error = null;
        try {
          const response = await fetch(uri);
          responseXML = await response.text();
        } catch (e) {
          error = e;
          console.error('ERROR SVG', e);
        } finally {
          if (this.isComponentMounted) {
            this.setState({svgXmlData: responseXML}, () => {
              const {onLoad} = this.props;
              if (onLoad && !error) {
                onLoad();
              }
            });
          }
        }
    
        return responseXML;
      }
    
      // Remove empty strings from children array
      trimElementChilden(children) {
        for (child of children) {
          if (typeof child === 'string') {
            if (child.trim().length === 0)
              children.splice(children.indexOf(child), 1);
          }
        }
      }
    
      createSVGElement(node, childs) {
        this.trimElementChilden(childs);
        let componentAtts = {};
        const i = ind++;
        switch (node.nodeName) {
          case 'svg':
            componentAtts = this.obtainComponentAtts(node, SVG_ATTS);
            if (this.props.width) {
              componentAtts.width = this.props.width;
            }
            if (this.props.height) {
              componentAtts.height = this.props.height;
            }
    
            return (
              <Svg key={i} {...componentAtts}>
                {childs}
              </Svg>
            );
          case 'g':
            componentAtts = this.obtainComponentAtts(node, G_ATTS);
            return (
              <G key={i} {...componentAtts}>
                {childs}
              </G>
            );
          case 'path':
            componentAtts = this.obtainComponentAtts(node, PATH_ATTS);
            return (
              <Path key={i} {...componentAtts}>
                {childs}
              </Path>
            );
          case 'circle':
            componentAtts = this.obtainComponentAtts(node, CIRCLE_ATTS);
            return (
              <Circle key={i} {...componentAtts}>
                {childs}
              </Circle>
            );
          case 'rect':
            componentAtts = this.obtainComponentAtts(node, RECT_ATTS);
            return (
              <Rect key={i} {...componentAtts}>
                {childs}
              </Rect>
            );
          case 'line':
            componentAtts = this.obtainComponentAtts(node, LINE_ATTS);
            return (
              <Line key={i} {...componentAtts}>
                {childs}
              </Line>
            );
          case 'defs':
            return <Defs key={i}>{childs}</Defs>;
          case 'linearGradient':
            componentAtts = this.obtainComponentAtts(node, LINEARG_ATTS);
            return (
              <LinearGradient key={i} {...componentAtts}>
                {childs}
              </LinearGradient>
            );
          case 'radialGradient':
            componentAtts = this.obtainComponentAtts(node, RADIALG_ATTS);
            return (
              <RadialGradient key={i} {...componentAtts}>
                {childs}
              </RadialGradient>
            );
          case 'stop':
            componentAtts = this.obtainComponentAtts(node, STOP_ATTS);
            return (
              <Stop key={i} {...componentAtts}>
                {childs}
              </Stop>
            );
          case 'ellipse':
            componentAtts = this.obtainComponentAtts(node, ELLIPSE_ATTS);
            return (
              <Ellipse key={i} {...componentAtts}>
                {childs}
              </Ellipse>
            );
          case 'polygon':
            componentAtts = this.obtainComponentAtts(node, POLYGON_ATTS);
            return (
              <Polygon key={i} {...componentAtts}>
                {childs}
              </Polygon>
            );
          case 'polyline':
            componentAtts = this.obtainComponentAtts(node, POLYLINE_ATTS);
            return (
              <Polyline key={i} {...componentAtts}>
                {childs}
              </Polyline>
            );
          case 'text':
            componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
            return (
              <Text key={i} {...componentAtts}>
                {childs}
              </Text>
            );
          case 'tspan':
            componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
            if (componentAtts.y) {
              componentAtts.y = fixYPosition(componentAtts.y, node);
            }
            return (
              <TSpan key={i} {...componentAtts}>
                {childs}
              </TSpan>
            );
          default:
            return null;
        }
      }
    
      obtainComponentAtts({attributes}, enabledAttributes) {
        const styleAtts = {};
    
        if (this.state.fill && this.props.fillAll) {
          styleAtts.fill = this.state.fill;
        }
    
        Array.from(attributes).forEach(({nodeName, nodeValue}) => {
          Object.assign(
            styleAtts,
            utils.transformStyle({
              nodeName,
              nodeValue,
              fillProp: this.state.fill,
            }),
          );
        });
    
        const componentAtts = Array.from(attributes)
          .map(utils.camelCaseNodeName)
          .map(utils.removePixelsFromNodeValue)
          .filter(utils.getEnabledAttributes(enabledAttributes.concat(COMMON_ATTS)))
          .reduce((acc, {nodeName, nodeValue}) => {
            acc[nodeName] =
              this.state.fill && nodeName === 'fill' && nodeValue !== 'none'
                ? this.state.fill
                : nodeValue;
            return acc;
          }, {});
        Object.assign(componentAtts, styleAtts);
    
        return componentAtts;
      }
    
      inspectNode(node) {
        // Only process accepted elements
        if (!ACCEPTED_SVG_ELEMENTS.includes(node.nodeName)) {
          return <View />;
        }
    
        // Process the xml node
        const arrayElements = [];
    
        // if have children process them.
        // Recursive function.
        if (node.childNodes && node.childNodes.length > 0) {
          for (let i = 0; i < node.childNodes.length; i++) {
            const isTextValue = node.childNodes[i].nodeValue;
            if (isTextValue) {
              arrayElements.push(node.childNodes[i].nodeValue);
            } else {
              const nodo = this.inspectNode(node.childNodes[i]);
              if (nodo != null) {
                arrayElements.push(nodo);
              }
            }
          }
        }
    
        return this.createSVGElement(node, arrayElements);
      }
    
      render() {
        try {
          if (this.state.svgXmlData == null) {
            return null;
          }
    
          const inputSVG = this.state.svgXmlData
            .substring(
              this.state.svgXmlData.indexOf('<svg '),
              this.state.svgXmlData.indexOf('</svg>') + 6,
            )
            .replace(/<!-(.*?)->/g, '');
    
          const doc = new xmldom.DOMParser().parseFromString(inputSVG);
    
          const rootSVG = this.inspectNode(doc.childNodes[0]);
    
          return <View style={this.props.style}>{rootSVG}</View>;
        } catch (e) {
          console.error('ERROR SVG', e);
          return null;
        }
      }
    }
    
    SvgUri.propTypes = {
      style: PropTypes.object,
      width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      svgXmlData: PropTypes.string,
      source: PropTypes.any,
      fill: PropTypes.string,
      onLoad: PropTypes.func,
      fillAll: PropTypes.bool,
    };
    
    module.exports = SvgUri;
    
    1. 直接把utils.js改名为svgUriUtils.js

    封装一个Svg组件

    注意:我这边导入的路径用了babel-plugin-module-resolver自定义的别名,需要根据实际情况修改的

    // Svg.js
    import React, {Component} from 'react';
    import SvgUri from '@utils/svgUri';
    import svgs from '@assets/icons';
    // import SvgUri from '../src/utils/svgUri';
    // import svgs from '../src/assets/icons';
    
    export default class Svg extends Component {
      render() {
        const {color, size, style, icon} = this.props;
    
        let svgXmlData = svgs[icon];
    
        if (!svgXmlData) {
          let err_msg = `没有"${icon}"这个icon,请下载最新的icomoo并 npm run build-js`;
          throw new Error(err_msg);
        }
        return (
          <SvgUri
            width={size}
            height={size}
            svgXmlData={svgXmlData}
            fill={color}
            style={style}
          />
        );
      }
    }
    

    最后在需要的地方导入并使用Svg组件即可

    import Svg from '@components/Svg'
    
    <Svg icon="earth" size="36" style={styles.positionIcon}></Svg>
    

    相关文章

      网友评论

          本文标题:在React-Native中使用SVG图片

          本文链接:https://www.haomeiwen.com/subject/hoevoktx.html