React-Native是不能直接引入SVG图片的,但是我们可以借助react-native-svg
和react-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.*?\?>|<\!--.*?-->|/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
去下载react-native-svg-uri
的源码,github上的地址是https://github.com/vault-development/react-native-svg-uri
注意,我们不必安装这个库,只要拿到下图中的两个js,修改以下为我们所用即可。
- 对
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 (
);
case 'g':
componentAtts = this.obtainComponentAtts(node, G_ATTS);
return (
{childs}
);
case 'path':
componentAtts = this.obtainComponentAtts(node, PATH_ATTS);
return (
{childs}
);
case 'circle':
componentAtts = this.obtainComponentAtts(node, CIRCLE_ATTS);
return (
{childs}
);
case 'rect':
componentAtts = this.obtainComponentAtts(node, RECT_ATTS);
return (
{childs}
);
case 'line':
componentAtts = this.obtainComponentAtts(node, LINE_ATTS);
return (
{childs}
);
case 'defs':
return {childs} ;
case 'linearGradient':
componentAtts = this.obtainComponentAtts(node, LINEARG_ATTS);
return (
{childs}
);
case 'radialGradient':
componentAtts = this.obtainComponentAtts(node, RADIALG_ATTS);
return (
{childs}
);
case 'stop':
componentAtts = this.obtainComponentAtts(node, STOP_ATTS);
return (
{childs}
);
case 'ellipse':
componentAtts = this.obtainComponentAtts(node, ELLIPSE_ATTS);
return (
{childs}
);
case 'polygon':
componentAtts = this.obtainComponentAtts(node, POLYGON_ATTS);
return (
{childs}
);
case 'polyline':
componentAtts = this.obtainComponentAtts(node, POLYLINE_ATTS);
return (
{childs}
);
case 'text':
componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
return (
{childs}
);
case 'tspan':
componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
if (componentAtts.y) {
componentAtts.y = fixYPosition(componentAtts.y, node);
}
return (
{childs}
);
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 ;
}
// 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('
- 直接把
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 (
);
}
}
最后在需要的地方导入并使用Svg组件即可
import Svg from '@components/Svg'