在React-Native中使用SVG图片

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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容