http://bbs.reactnative.cn/topic/306/%E5%9C%A8react-native%E4%B8%AD%E4%BD%BF%E7%94%A8arthtml
前半个月捣腾了一下React Native ART
如今手上闲下来了和你们分享一下React Native中的ART使用心得前端
所谓ART,是一个在React中绘制矢量图形的JS类库。这个类库抽象了一系统的通用接口,统一了SVG,canvas,VML这类矢量图形在 React中的书写格式。你能够经过ART将SVG,canvas,VML的矢量图形拿到React中使用,也能够把ART反转回去。(虽然有不少局限 性,后面会讲到)
React Native ART 是react-art在React Native中的移植版,接口几乎彻底一致,
React Native中的ART很早以前就已经开源了iOS版,最近又在0.18.0中开源了Android版本
由于缺乏官方文档,一直不为人所知。react
我在前端页面切片的时候比较喜欢用SVG,有条件我都尽可能让设计师出AI的设计稿或者把图标导成SVG的
由于SVG和图片相比,优势太多太多:能够代码复用,能够无损放大,经过合理导出的svg代码比同等压缩过的图片文件大小要小不少,并且svg代码还能够经过gzip压缩
最近在作一个原生内嵌的React Native项目,设计师是按照原生APP的标准把每一个图标切了3个尺寸给我,感谢React Native中强大的打包工具,能够很方便的使用这写图标。因此项目开始也没想着要用SVG(当时android的ART还没开源,这也是一个重要因 素)。
在整个项目完成以后,看了一下,全部图片在通过压缩以后加起来有200多K,大小还能够接受。
可是在项目后期优化过程当中,发现一个使用图片图标很是致命的问题:android
<Image />元素在iOS下不是同步渲染的,即便是使用的本地图片资源!
更过度的是具备相同source
的多个Image也不能在同一帧里面渲染出来的,以下的5个图标,在iOS下它们不会同时被渲 染出来,这样形成的影响是在打开一个列表的时候,看见这些图标一个接一个无序的跳出来,这个效果若是在iphone4s,5s下看至关的明显,很恶心,完 全不能忍受!想过各类方法也没办法绕过去。
git
首屏的以下几个图标以及返回按钮和logo,还有TabBar里面的图标在iOS下都是在界面出现的若干帧以后再无序的跳出来,这样的效果很很差,一点都没有原生APP的感受,有一种hybird的既视感。
github
后来等到了0.18.0,Android的ART对外开源了,我想到为何不用SVG图标尝试一下呢?
而后让设计师把这个图标:
从新切成SVG给我,而后再把SVG转成ART。
在iOS下完美渲染!全部图标和界面文字一块儿出现,不论是放5个仍是100个。并且对界面的总体渲染速度也没有任何能感知到的影响。
在安卓下也是一样的完美,并且生成的ART代码要比图片小了不少。npm
<Surface width={50} height={50} > <Group scale={0.23}> <Shape fill={`${this.props.active ? '#ff5942' : '#ccc'}`} d={`M46.31,0H3.69C1.63,0,0,1.63,0,3.69v42.63C0,48.37,1.63,50,3.69,50h42.63c2.05,0,3.69-1.63,3.69-3.69V3.69 C50,1.63,48.37,0,46.31,0z M44.5,22.92l-7.76,7.65l1.8,10.61c0.18,1.07-0.29,2.14-1.21,2.77c-0.51,0.34-1.1,0.52-1.69,0.52 c-0.49,0-0.98-0.12-1.42-0.35l-9.23-4.75l-9.23,4.75c-0.44,0.24-0.94,0.35-1.42,0.35c-0.6,0-1.19-0.17-1.69-0.52 c-0.92-0.63-1.38-1.69-1.21-2.77l1.8-10.61l-7.76-7.65c-0.77-0.76-1.04-1.86-0.69-2.87c0.35-1.01,1.25-1.73,2.34-1.9l10.6-1.55 l4.6-9.43c0.49-1,1.52-1.62,2.66-1.62c1.15,0,2.18,0.64,2.66,1.62l4.6,9.43l10.59,1.55c1.09,0.16,1.99,0.9,2.34,1.9 C45.53,21.06,45.27,22.16,44.5,22.92z`} /> </Group> </Surface>
这里面的路径代码是经过PS CC直接导出的,没有作任何优化,若是再优化一番能够再缩小一半。SVG代码优化后面再作介绍。
下面正式进入正题canvas
安装ART很简单
安卓里面根本不用安装,0.18.0里面自带,直接在js引入既可react-native
import React, {ART} from 'react-native'; const {Surface, Group, Shape} = ART;
iOS里面的ART是可选的,你须要手动导入xcodeproj文件,以及加入静态连接库
可是也很简单:教程xcode
请你们原谅我,其实我是标题党,我不会在这里详细的讲解ART的用法,而是讲一个基于ART的SVG库:react-native-art-svg
为何我不在这里讲ART呢?缘由以下
上面那段代码是ART代码和设计师给我svg代码其实有一些差别,还要把SVG转换一次,很不友好,由于ART的元素和SVG元素名称和用法都不太同样
在SVG代码里面,画布元素是<svg>而ART里面是<Surface>
绘制路径的元素在SVG里面是<path>,而ART里面的Path又是另一个东西,在ART里面须要<Shape>
这都不是最麻烦的地方,一些基础的SVG图形<circle>、<rect>、<polygon>...等等的元素ART都是不支持的。
前端切图,不论是网页仍是React Native的界面,设计稿和图标都是设计师给咱们的(部分连设计都一块儿作了的大神除外)
而设计师给咱们的矢量图标要么是AI原图要么是SVG,可是最终到咱们手里,导出来一定是SVG。
因此ART虽然经过抽象接口来统一了SVG,canvas,VML在React中的使用,可是ART提供的接口仍是至关不友好的。而后我就萌发了一个想法,为何再也不把ART封装一层,把语法封装成和SVG的语法呢?
而后react-native-art-svg就诞生了,支持SVG全部经常使用的元素和属性,svg代码拿过来几乎能够直接使用(后面会添加直接导入svg代码的功能)
预览图以下:
安装很简单,由于是纯JavaScript的类库,直接一行搞定
npm i react-native-art-svg --save
经过以下代码在js中引入
import Svg, { Circle, Ellipse, G, LinearGradient, RadialGradient, Line, Path, Polygon, Polyline, Rect, Symbol, Text, Use, Defs, Stop } from 'react-native-art-svg';
上面的ART代码等同于以下的代码
<Svg width={50} height={50} > <G scale={0.23}> <Path fill={`${this.props.active ? '#ff5942' : '#ccc'}`} d={`M46.31,0H3.69C1.63,0,0,1.63,0,3.69v42.63C0,48.37,1.63,50,3.69,50h42.63c2.05,0,3.69-1.63,3.69-3.69V3.69 C50,1.63,48.37,0,46.31,0z M44.5,22.92l-7.76,7.65l1.8,10.61c0.18,1.07-0.29,2.14-1.21,2.77c-0.51,0.34-1.1,0.52-1.69,0.52 c-0.49,0-0.98-0.12-1.42-0.35l-9.23-4.75l-9.23,4.75c-0.44,0.24-0.94,0.35-1.42,0.35c-0.6,0-1.19-0.17-1.69-0.52 c-0.92-0.63-1.38-1.69-1.21-2.77l1.8-10.61l-7.76-7.65c-0.77-0.76-1.04-1.86-0.69-2.87c0.35-1.01,1.25-1.73,2.34-1.9l10.6-1.55 l4.6-9.43c0.49-1,1.52-1.62,2.66-1.62c1.15,0,2.18,0.64,2.66,1.62l4.6,9.43l10.59,1.55c1.09,0.16,1.99,0.9,2.34,1.9 C45.53,21.06,45.27,22.16,44.5,22.92z`} /> </G> </Svg>
这段代码从SVG转过来可省心多了,只须要改一下元素的大小写,把元素名称改为大写字母开头的既可,属性名称改为驼峰式的命名方式。
通用属性列表:
属性名称 | 默认值 | 描述 |
---|---|---|
fill | '#000' | 内部填充规则(填充颜色,填充渐变图形) |
fillOpacity | 1 | 填充的透明度 |
stroke | 'none' | 描边颜色 |
strokeWidth | 1 | 描边的宽度 |
strokeOpacity | 1 | 描边的透明度 |
strokeLinecap | 'square' | 描边线段端点显示方式 |
strokeLinejoin | 'miter' | 描边线段链接处的显示方式 |
strokeDasharray | [] | 描边线段断点显示规则 |
x | 0 | 当前图形x轴偏移量 |
y | 0 | 当前图形y轴偏移量 |
rotate | 0 | 当前图形旋转值 |
scale | 1 | 当前图形的缩放值 |
origin | 0, 0 | 变形原点(x,y,rotate,scale的变形原点坐标) |
originX | 0 | 变形原点x轴坐标 |
originY | 0 | 变形原点y轴坐标 |
能够看到,几乎全部SVG的经常使用属性均可以支持
不支持fillRule,由于官方认为fillRule不是SVG,canvas和VML公用的属性,他们不对这类独有的属性进行支持
暂不支持clipPath,由于ART官方还未完成
支持的元素列表:
Svg为全部矢量图形的画布元素,全部Svg内的矢量图形都会绘制在Svg内部
而且Svg支持viewbox和preserveAspectRatio属性(关于这两个属性的详解)
<Svg height="100" width="100" > <Rect x="0" y="0" width="100" height="100" fill="black" /> <Circle cx="50" cy="50" r="30" fill="yellow" /> <Circle cx="40" cy="40" r="4" fill="black" /> <Circle cx="60" cy="40" r="4" fill="black" /> <Path d="M 40 60 A 10 10 0 0 0 60 60" stroke="black" /> </Svg>;
上面的代码会绘制以下的图形
其中height和width定义了Svg元素的宽高
注意
在react-native-art-svg中,全部元素的数字类型的属性均可以直接用字符串表示,如:
下面两段属性声明均可以正常使用,这样写起来很方便
height="100"
height={100}
<Rect>元素能够在画布上绘制一个矩形,矩形位置和大小经过x,y,width,height属性共同定义
<Svg width="200" height="60" > <Rect x="25" y="5" width="150" height="50" fill="rgb(0,0,255)" strokeWidth="3" stroke="rgb(0,0,0)" /> </Svg>
上面的代码绘制了一个左上角位于画布25,5,宽150,高50的矩形
而且使用蓝色(rgb(0,0,255))填充(fill)内部背景
用黑色(rgb(0,0,0))进行描边(stroke),描边宽度(strokeWidth)为3
<Circle>元素能够在画布上绘制一个圆形,圆型须要cx,cy,r三个属性,分别对应圆形x轴坐标,y轴坐标以及半径
<Svg height="100" width="110" > <Circle cx="50" cy="50" r="50" fill="pink" /> </Svg>
<Ellipse>元素能够在画布上绘制一个椭圆,椭圆和圆形相似,只是把圆形的r属性替换成了分别对应水平半径和垂直半径的rx,ry属性,rx == ry的椭圆是一个圆形
<Svg height="100" width="110" > <Ellipse cx="55" cy="55" rx="50" ry="30" stroke="purple" strokeWidth="2" fill="yellow" /> </Svg>
<Line>元素能够在画布上画一条从x1,y1到x2,y2的直线
<Svg height="100" width="100" > <Line x1="0" y1="0" x2="100" y2="100" stroke="red" strokeWidth="2" /> </Svg>
<Polygon>元素能够在画布上画一个由多个点收尾相接组合而成的多边形,每一个点的坐标由空格分开定义在points属性中
<Svg height="100" width="100" > <Polyline points="10,10 20,12 30,20 40,60 60,70 95,90" fill="none" stroke="black" strokeWidth="3" /> </Svg>
<Polyline>元素和多边形类型,只是多边形的最后一个点和第一个点是相连的,组成的是一个闭合的图形,多边线的最后一个点和第一个点没有链接
<Svg height="100" width="100" > <Polyline points="10,10 20,12 30,20 40,60 60,70 95,90" fill="none" stroke="black" strokeWidth="3" /> </Svg>
<Path>的d
属性内定义了一系列的路径坐标以及绘制规则命令,上面的全部图形均可以经过Path绘制而成
注意: 上面全部的命令均可以使用小写字母,大写字母的命令是使用的坐标是绝对值,小写字母的命令的坐标是相对值
<Svg height="100" width="100" > <Path d="M25 10 L98 65 L70 25 L16 77 L11 30 L0 4 L90 50 L50 10 L11 22 L77 95 L20 25" fill="none" stroke="red" /> </Svg>
<Text>元素能够在画布上绘制文字图形
<Svg height="60" width="200" > <Text fill="none" stroke="purple" fontSize="20" fontWeight="bold" x="100" y="20" textAnchor="center" >STROKED TEXT</Text> </Svg
<G>元素能够把它内部的子元素组合成一个分组,G元素内全部的子元素都将继承除(id,变形属性以外的全部属性)
下面两段代是相同的
<Svg height="60" width="200" > <G width="100" height="30" fill="blue" x="0"> <Rect y="0" /> <Rect y="30" /> </G> </Svg> <Svg height="60" width="200" > <G> <Rect width="100" height="30" fill="blue" x="0" y="0" /> <Rect width="100" height="30" fill="blue" x="0" y="0" /> </G> </Svg>
G元素的变形属性不会被继承
<Svg height="100" width="200" > <G rotate="50" origin="100, 50" > <Line x1="60" y1="10" x2="140" y2="10" stroke="#060" /> <Rect x="60" y="20" height="50" width="80" stroke="#060" fill="#060" /> <Text x="100" y="75" stroke="#600" fill="#600" textAnchor="center" > Text grouped with shapes</Text> </G> </Svg>
<Defs>元素内的全部图形都不会被绘制在画布上,<Defs>元素用于定义一些代码复用相关的元素,下面使用其余元素解释<Defs>元素的用法
使用<Use>元素能够进行代码复用,能够将<Defs>元素中定义的元素复制一份
绘制在当前<Use>元素的位置
<Svg height="100" width="300" > <Defs> <G id="shape"> <Circle cx="50" cy="50" r="50" /> <Rect x="50" y="50" width="50" height="50" /> <Circle cx="50" cy="50" r="5" fill="blue" /> </G> </Defs> <Use href="#shape" x="20" y="0"/> <Use href="#shape" x="170"y="0" /> </Svg>
*注意:*Use元素使用href指定复用对象(SVG中是使用的xlink:href=),属性值为#
+须要复用元素的id,也能够复用<Defs>外面具备id属性的元素,Use元素上的属性将覆盖被复用元素的属性
使用<Symbol>元素能够更快捷的定义元素,使用<Defs>元素定义可复用元素时,每一个子元素对应一个可复用元素, 若是须要复用多个元素组成的图形时一般使用<Symbol>元素进行定义,<Symbol>自身也不会被绘制在画布上,并且能够 给Symbol指定viewbox和preserveAspectRatio属性,更为方便的对图形进行缩放
<Svg height="150" width="110" > <Symbol id="symbol" viewbox="0 0 150 110" width="100" height="50"> <Circle cx="50" cy="50" r="40" strokeWidth="8" stroke="red" fill="red"/> <Circle cx="90" cy="60" r="40" strokeWidth="8" stroke="green" fill="white"/> </Symbol> <Use href="#symbol" x="0" y="0" /> <Use href="#symbol" x="0" y="50" width="75" height="38" /> <Use href="#symbol" x="0" y="100" width="50" height="25" /> </Svg>
<LinearGradient>能够定义一个线性渐变的填充规则,改元素必须定义在<Defs>元素内。
线性渐变能够被定义为水平渐变、垂直渐变和带角度的渐变
经过调整x1,y1设置渐变开始点,x2,y2设置渐变结束点
使用<Stop>元素定义渐变的变色点
<Svg height="150" width="300" > <Defs> <LinearGradient id="grad" x1="0" y1="0" x2="170" y2="0"> <Stop offset="0" stopColor="rgb(255,255,0)" stopOpacity="0" /> <Stop offset="1" stopColor="red" stopOpacity="1" /> </LinearGradient> </Defs> <Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" /> </Svg>
注意:<LinearGradient>和<RadialGradient>的x1,y2,x2,y2属性都可以使用百分比
下面<LinearGradient>绘制的结果等同于上面代码的绘制结果
<LinearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%"> <Stop offset="0%" stopColor="rgb(255,255,0)" stopOpacity="0" /> <Stop offset="100%" stopColor="red" stopOpacity="1" /> </LinearGradient>
<RadialGradient>能够定义一个线性渐变的填充规则,和<LinearGradient>同样改元素必须定义在<Defs>元素内。
cx,cu,rx,ry属性定义了最外层渐变椭圆的位置坐标和大小,fx和fy属性定义了最内层渐变椭圆的位置坐标
<Svg height="150" width="300" > <Defs> <RadialGradient id="grad" cx="150" cy="75" rx="85" ry="55" fx="150" fy="75"> <Stop offset="0" stopColor="#ff0" stopOpacity="1" /> <Stop offset="1" stopColor="#83a" stopOpacity="1" /> </RadialGradient> </Defs> <Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" /> </Svg>
好了,差很少就这些了。
上面包含了大多数react-native-art-svg
类库的用法,想看更具体的例子请fork以后运行Example下面的示例工程。
让咱们再回到在文章开头的那个项目中去,经过几番折腾,终于把项目中的全部图片都换成了矢量图形,在iOS下再也没有出现图片闪动或图片不能和界面同时渲染的问题了。
并且SVG代码再通过一番优化以后,把200多K的图片变成了30多K的矢量代码。完美!
我再顺带讲一下SVG代码优化:
局中描边
,由于只有局中描边导出的SVG代码会保留原始设计稿中的图形信息,用外层描边或内侧描边生成的SVG代码都是经过<path>元素绘制的,这样就会使得SVG代码成倍的增长。