为什么添加SVG支持?
可伸缩矢量图形 (Scalable Vector Graphics)简称SVG,相对于普通的PNG图形文件,SVG格式有更好的展示效果,可以明显增加用户的体验。FoxBPM流程引擎同时支持PNG和SVG两种格式的输出。开发者可以根据用户需求动态的抉择。针对BPM产品SVG本身有着更多的优势:首先SVG本质就是一个XML,其对应的字符串可以直接在支持SVG的浏览器中展示,所以相对普通的图形文件SVG可以更加容易的输出到前端浏览器,在网络带宽不足的情况下这个优势就更加明显; 其次既然SVG本身是一个XML,那么我们就可以在前端像操作其他的DOM模型一样操作SVG,从而我们在浏览器前端也可以动态的改变流程图内容,从而添加更多功能(比如流程实例的运行轨迹,运行状态等)。
主要知识点和难点:
主要知识点包括:采用JAXB实现XML 和POJO之间的映射,VO的克隆,三次贝塞尔曲线的控制点计算,线条拐点的中心点计算等等。相关知识点核心代码如下所示:
VO对象克隆方法:
/** * DefsVO克隆 * @param DefsVO 原对象 * @return clone之后的对象 */ public final static GVO cloneGVO(GVO gVo) { return (GVO) clone(gVo); } public final static DefsVO cloneDefsVO(DefsVO defsVo) { return (DefsVO) clone(defsVo); } /** * SvgVO模板对象需要多次引用,所以要克隆,避免产生问题 * @param SvgVO 原对象 * @return clone之后的对象 */ public final static SvgVO cloneSVGVo(SvgVO svgVo) { return (SvgVO) clone(svgVo); } /** * 克隆对象 * @param object 原对象 * @return 目标对象 */ public final static Object clone(Object object) { ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ObjectInputStream ois = null; Object cloneObject = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(object); ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); cloneObject = ois.readObject(); } catch (Exception e) { throw new FoxBPMException("SVG对象G节点克隆出现问题", e); } finally { try { if (bos != null) { bos.close(); } if (oos != null) { oos.close(); } if (ois != null) { ois.close(); } } catch (Exception e) { throw new FoxBPMException("克隆之后关闭对象流时出现问题", e); } } return cloneObject; }
JAXB映射方法(templateName是bpmn2.0官网提供的SVG模板名称):
/** * 第一次需要从svg文档加载 * * @param templateName */ private void init(String templateName) { try { JAXBContext context = JAXBContext.newInstance(SvgVO.class); Unmarshaller unMarshaller = context.createUnmarshaller(); SAXParserFactory factory = SAXParserFactory.newInstance(); // 解析的时候忽略SVG命名空间,否则会出错 factory.setNamespaceAware(true); XMLReader reader = factory.newSAXParser().getXMLReader(); String sourcePath = new StringBuffer(BPMN_PATH).append(FILE_SPERATOR) .append(templateName).toString(); Source source = new SAXSource(reader, new InputSource( ReflectUtil.getResourceAsStream(sourcePath))); VONode object = (VONode) unMarshaller.unmarshal(source); //将模板VO对象加入系统缓存 this.svgTemplets.put(templateName, object); } catch (Exception e) { throw new FoxBPMException("template svg file load exception", e); } finally { } } /** * 操作之后的SVG转化成String字符串 * @param svgVo 容器对象 * @return SVG字符串 */ public final static String createSVGString(VONode svgVo) { try { JAXBContext context = JAXBContext.newInstance(SvgVO.class); Marshaller marshal = context.createMarshaller(); marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); StringWriter writer = new StringWriter(); marshal.marshal(svgVo, writer); return writer.toString(); } catch (Exception e) { throw new FoxBPMException("svg object convert to String exception", e); } }
三次贝塞尔曲线的控制点以及线条中心点计算方法:
/** * Copyright 1996-2014 FoxBPM ORG. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author MAENLIANG */ package org.foxbpm.engine.impl.diagramview.svg; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * 坐标工具类 * @author MAENLIANG * @date 2014-06-19 * */ public final class PointUtils { /** * X、Y允许的最大偏移量,超过最大偏移量需要设置文本相对线条的中心位置,如果小于这个 * 值则不需要设置 */ private static final float X_Y_LOCATION_MAXSHIFT = 100F; // TODO后续改善成动态规划算法 /** * 默认圆角的大小,暂时分三个梯度 */ private final static float SEQUENCE_ROUNDCONTROL_FLAG = 15.0f; private final static float SEQUENCE_ROUNDCONTROL_MIN_FLAG = 3.0f; private final static float NONE_SCALE = 1.0f; /** * 计算三次贝塞尔曲线的起始点,控制点以及终点 * @param start * @param center * @param end * @return 起始点 控制点 终点坐标数组 */ public final static Point[] caclBeralPoints(Point start, Point center, Point end) { float lengthStartHerizon = 0.0f; float lengthStartVertical = 0.0f; float lengthStartCenter = 0.0f; float lengthEndHerizon = 0.0f; float lengthEndVertical = 0.0f; float lengthEndCenter = 0.0f; lengthStartHerizon = Math.abs(start.getX() - center.getX()); lengthStartVertical = Math.abs(start.getY() - center.getY()); lengthStartCenter = (float) Math.sqrt(lengthStartHerizon * lengthStartHerizon + lengthStartVertical * lengthStartVertical); lengthEndHerizon = Math.abs(end.getX() - center.getX()); lengthEndVertical = Math.abs(end.getY() - center.getY()); lengthEndCenter = (float) Math.sqrt(lengthEndHerizon * lengthEndHerizon + lengthEndVertical * lengthEndVertical); float scale = SEQUENCE_ROUNDCONTROL_FLAG; if (lengthEndCenter <= SEQUENCE_ROUNDCONTROL_FLAG || lengthStartCenter <= SEQUENCE_ROUNDCONTROL_FLAG) { if (lengthEndCenter <= SEQUENCE_ROUNDCONTROL_MIN_FLAG || lengthStartCenter <= SEQUENCE_ROUNDCONTROL_MIN_FLAG) { scale = NONE_SCALE; } else { scale = SEQUENCE_ROUNDCONTROL_MIN_FLAG; } } float controlScale = scale / 3; Point[] points = new Point[4]; points[0] = caclBeralControlPoint(start, center, scale, lengthStartHerizon, lengthStartVertical, lengthStartCenter); points[1] = caclBeralControlPoint(start, center, controlScale, lengthStartHerizon, lengthStartVertical, lengthStartCenter); points[2] = caclBeralControlPoint(end, center, controlScale, lengthEndHerizon, lengthEndVertical, lengthEndCenter); points[3] = caclBeralControlPoint(end, center, scale, lengthEndHerizon, lengthEndVertical, lengthEndCenter); return points; } /** * 计算三次贝塞尔曲线控制点 * * @param startEndPoint * @param center * @return */ private final static Point caclBeralControlPoint(Point startEndPoint, Point center, float scale, float lengthHerizon, float lengthVertical, float lengthCenter) { float startX = 0.0f; float startY = 0.0f; if (startEndPoint.getX() > center.getX()) { startX = ((scale * lengthHerizon) / lengthCenter) + center.getX(); } else { startX = (((lengthCenter - scale) * lengthHerizon) / lengthCenter) + startEndPoint.getX(); } if (startEndPoint.getY() > center.getY()) { startY = ((scale * lengthVertical) / lengthCenter) + center.getY(); } else { startY = (((lengthCenter - scale) * lengthVertical) / lengthCenter) + startEndPoint.getY(); } Point resultPoint = new Point(Math.round(startX), Math.round(startY)); return resultPoint; } /** * 计算中心点位置,包括复杂情况,和简单情况 * * @param pointList * @return */ public final static Point caclDetailCenterPoint(List<Point> pointList) { Float[][] xyArrays = getXYLocationArray(pointList); Float[] xArrays = xyArrays[0]; Float[] yArrays = xyArrays[1]; float xShift = calCariance(xArrays); float yShift = calCariance(yArrays); // 如果偏差过大就计算中心点 if (xShift > X_Y_LOCATION_MAXSHIFT && yShift < X_Y_LOCATION_MAXSHIFT) { // X坐标拐点幅度大,Y不大,计算线条X中心位置,Y取平均值 return caclXCenter(xArrays, yArrays); } else if (yShift > X_Y_LOCATION_MAXSHIFT && xShift < X_Y_LOCATION_MAXSHIFT) { // Y坐标拐点幅度大,X不大,计算线条Y中心位置,X取平均值 return caclYCenter(xArrays, yArrays); } else if (yShift > X_Y_LOCATION_MAXSHIFT && xShift > X_Y_LOCATION_MAXSHIFT) { // XY拐点幅度都比较大 return caclCenterPoint(pointList); } // 如果没有计算中心点 return null; } /** * 勾股定理, 取线段长度 * * @param pointA * @param pointB * @return */ public final static float segmentLength(Point pointA, Point pointB) { double length = 0; float width = pointA.getX() - pointA.getX(); float height = pointA.getY() - pointB.getY(); double dWidth = (double) width; double dHeight = (double) height; length = Math.sqrt(Math.pow(dWidth, 2) + Math.pow(dHeight, 2)); return (float) length; } /** * 统计所有线段的长度 * * @param pointList * @return */ private final static List<Float> getSegmentsLength(List<Point> pointList) { List<Float> segList = new ArrayList<Float>(); float segLen = 0f; int size = pointList.size(); size = size - 1; Point pointA = null; Point pointB = null; for (int i = 0; i < size; i++) { pointA = pointList.get(i); pointB = pointList.get(i + 1); segLen = segmentLength(pointA, pointB); segList.add(segLen); } return segList; } /** * 计算所有x坐标值或者y坐标值的方差,判断是否需要重新设置所有拐点的中心点 * * @param array * @return */ public final static Float calCariance(Float[] array) { int arrayLength = array.length; Float ave = 0.0F; for (int i = 0; i < arrayLength; i++) { ave += array[i]; } ave /= arrayLength; Float sum = 0.0F; for (int i = 0; i < arrayLength; i++) { sum += (array[i] - ave) * (array[i] - ave); } sum = sum / arrayLength; return sum; } /** * 获取所有点的XY坐标 * * @param pointList * @return */ private final static Float[][] getXYLocationArray(List<Point> pointList) { int pointListsize = pointList.size(); Float[][] xyLocationArray = new Float[2][pointListsize]; Iterator<Point> iterator = pointList.iterator(); for (int i = 0; i < pointListsize; i++) { Point next = iterator.next(); xyLocationArray[0][i] = next.getX(); xyLocationArray[1][i] = next.getY(); } return xyLocationArray; } /** * 计算线条在X方向上的中心位置 * * @param xArrays * @param yArrays * @return */ public final static Point caclXCenter(Float[] xArrays, Float[] yArrays) { Arrays.sort(xArrays); int length = yArrays.length; Float xCenter = xArrays[0] + (xArrays[xArrays.length - 1] - xArrays[0]) / 2; Float tempV = 0.0f; for (int i = 0; i < length; i++) { tempV += yArrays[i]; } Float y = tempV / length; return new Point(xCenter, y); } /** * 计算线条在y方向上的中心位置 * * @param xArrays * @param yArrays * @return */ public final static Point caclYCenter(Float[] xArrays, Float[] yArrays) { Arrays.sort(yArrays); Float yCenter = yArrays[0] + (yArrays[yArrays.length - 1] - yArrays[0]) / 2; Float tempV = 0.0f; int length = xArrays.length; for (int i = 0; i < length; i++) { tempV += xArrays[i]; } Float x = tempV / length; return new Point(yCenter, x); } /** * 计算中心点 * * @param svgLine * @return */ public final static Point caclCenterPoint(List<Point> pointList) { List<Float> segList = PointUtils.getSegmentsLength(pointList); float totalLength = 0f; for (float seg : segList) { totalLength += seg; } float centerLen = totalLength / 2; int keySegIndex = 0; float keySeg = 0f; float tempLen = 0f; for (float seg : segList) { keySeg = seg; keySegIndex++; if ((tempLen + keySeg) > centerLen) { break; } else { tempLen += keySeg; } } float lastLen = centerLen - tempLen; Point startPoint = pointList.get(keySegIndex - 1); Point endPoint = pointList.get(keySegIndex); float scale = lastLen / keySeg; float x = startPoint.getX() + (endPoint.getX() - startPoint.getX()) * scale; float y = startPoint.getY() + (endPoint.getY() - startPoint.getY()) * scale; Point point = new Point(x, y); return point; } }
模块结构详细说明:
结构如下图所示:
1、模块构建基于工厂和创建者设计模式,独立于SVG,对其他矢量标记语言扩展友好。
2、FoxBpmnViewBuilder为VO属性构建的上层接口,当创建新的元素时,可以针对元素的特性进行实现。流程引擎后期的维护和功能扩展也主要是针对该接口的实现。
3、AbstractFlowElementVOFactory也是VO属性构建的上层抽象类,负责VO对象的获取、过滤、以及根据特定元素选择特定Builder构建VO属性。
4、svg包模块是针对FoxBpmnViewBuilder和AbstractFlowElementVOFactory的svg实现,包括svg工厂、svg 构建者、svg VO模型、svg工具类。
SVG构建详细说明:
1、流程引擎启动初始化的时候,将BPMN2.0官网提供的所有SVG模版(其模版以单个组件的形式提供)初始化,采用映射工具JAXB 将模版转化成VO对象并且加入缓存。
VO定义如下代码所示:
/** * Copyright 1996-2014 FoxBPM ORG. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author MAENLIANG */ package org.foxbpm.engine.impl.diagramview.vo; import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.XmlValue; /** * SVG对象的超类,包括 节点SVG对象,连接线SVG对象 * * @author MAENLIANG * @date 2014-06-10 * */ @XmlType @XmlAccessorType(XmlAccessType.FIELD) public abstract class VONode implements Serializable { private static final long serialVersionUID = -817550474604039751L; /** * ID Name 用于标记流程元素ID用于 流程图的前端操作 */ @XmlAttribute(name = "id") protected String id; @XmlAttribute(name = "name") protected String name; @XmlAttribute(name = "style") protected String style; @XmlAttribute(name = "fill") protected String fill; @XmlAttribute(name = "stroke") protected String stroke; @XmlAttribute(name = "stroke-width") protected Float strokeWidth; @XmlAttribute(name = "stroke-linecap") protected String strokeLinecap; @XmlAttribute(name = "stroke-linejoin") protected String strokeLinejoin; @XmlAttribute(name = "width") protected Float width; @XmlAttribute(name = "height") protected Float height; @XmlAttribute(name = "x") protected Float x; @XmlAttribute(name = "y") protected Float y; @XmlValue protected String elementValue; public String getElementValue() { return elementValue; } public void setElementValue(String elementValue) { this.elementValue = elementValue; } public Float getX() { return x; } public void setX(Float x) { this.x = x; } public Float getY() { return y; } public void setY(Float y) { this.y = y; } public String getStyle() { return style; } public void setStyle(String style) { this.style = style; } public String getFill() { return fill; } public void setFill(String fill) { this.fill = fill; } public String getStroke() { return stroke; } public void setStroke(String stroke) { this.stroke = stroke; } public float getStrokeWidth() { return strokeWidth; } public void setStrokeWidth(float strokeWidth) { this.strokeWidth = strokeWidth; } public float getWidth() { return width; } public void setWidth(float width) { this.width = width; } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getStrokeLinecap() { return strokeLinecap; } public void setStrokeLinecap(String strokeLinecap) { this.strokeLinecap = strokeLinecap; } public String getStrokeLinejoin() { return strokeLinejoin; } public void setStrokeLinejoin(String strokeLinejoin) { this.strokeLinejoin = strokeLinejoin; } }
2、SVG模版相对独立,当创建时流程引擎提供包含所有流程元素定义的流程定义对象,流程元素包括 节点、线条、泳道、部件,其中节点包括活动任务、事件、网关。SVG创建者首先创建一个VO集合,然后根据流程元素从缓存中获取每个元素对应的自己的VO对象,获取VO 对象之后根据每个元素的式样属性来设置其VO属性,需要实现的接口为FoxBpmnViewBuilder,VO对象属性设置好之后添加到VO集合中。
FoxBpmnViewBuilder代码如下:
/** * Copyright 1996-2014 FoxBPM ORG. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author MAENLIANG */ package org.foxbpm.engine.impl.diagramview.builder; import java.util.List; import org.foxbpm.engine.impl.diagramview.svg.Point; /** * 流程图形信息构造接口 * * @author MAENLIANG * @date 2014-06-10 */ public interface FoxBpmnViewBuilder { public static final String SEQUENCE_STROKEWIDTH_DEFAULT = "2"; public static final String COLOR_FLAG = "#"; public static final String STROKE_DEFAULT = "black"; public static final String STROKEWIDTH_DEFAULT = "1"; public static final int LINEARGRADIENT_INDEX = 0; public static final String COMMA = ","; public static final String BACK_GROUND_PREFIX = "url(#"; public static final String BACK_GROUND_SUFFIX = ") #"; public static final String BRACKET_SUFFIX = ")"; public static final String TRANSLANT_PREFIX = "translate("; public static final String ARIAL = "arial"; /** * 设置节点信息 */ public void setWayPoints(List<Point> pointList); public void setWidth(float width); public void setHeight(float height); public void setXAndY(float x, float y); public void setStroke(String stroke); public void setStrokeWidth(float strokeWidth); public void setFill(String fill); public void setID(String id); public void setName(String name); public void setStyle(String style); /** * 设置文本信息 */ public void setText(String text); public void setTextFont(String font); public void setTextStrokeWidth(float textStrokeWidth); public void setTextX(float textX); public void setTextY(float textY); public void setTextFontSize(String textFontSize); public void setTextStroke(String textStroke); public void setTextFill(String textFill); /** * 设置子类型 */ public void setTypeStroke(String stroke); public void setTypeStrokeWidth(float strokeWidth); public void setTypeFill(String fill); public void setTypeStyle(String style); /** * 泳道负责重写该方法 setTextLocationByHerizonFlag */ public void setTextLocationByHerizonFlag(boolean herizonFlag); }
3、所有元素的VO对象设置好之后,再根据流程定义提供的基本信息创建SVG模版对象,然后将所有的VO对象克隆并且添加到模版对象,然后将模版对象用JAXB 工具转化成最终的SVG XML文档
流程定义对象所包含的元素如下所以:
List<KernelFlowNodeImpl> flowNodes = new ArrayList<KernelFlowNodeImpl>(); Map<String, KernelSequenceFlowImpl> sequenceFlows = new HashMap<String, KernelSeque nceFlowImpl>(); List<KernelLaneSet> laneSets = new ArrayList<KernelLaneSet>(); List<KernelArtifact> artifacts = new ArrayList<KernelArtifact>();
foxbpm流程引擎构建SVG示意图如下:
相关推荐
"使用 Vite-SVG-Loader 实现 SVG 图像的轻量级引入" 在现代 Web 开发中,使用 SVG 图像已经成为一个非常常见的需求,特别是在 Vue 应用程序中。然而,引入 SVG 图像经常会遇到一些问题,例如图像大小问题、浏览器...
赠送jar包:jaxb-svg11-1.0.2.jar; 赠送原API文档:jaxb-svg11-1.0.2-javadoc.jar; 赠送源代码:jaxb-svg11-1.0.2-sources.jar; 赠送Maven依赖信息文件:jaxb-svg11-1.0.2.pom; 包含翻译后的API文档:jaxb-svg11...
《Photoshop到SVG转换脚本详解——save-ps-to-svg 1.0》 在数字艺术与设计领域,Photoshop(简称PS)是一款广泛使用的图像处理软件,而SVG(Scalable Vector Graphics)则是一种基于XML的矢量图形格式,尤其适用于...
SVG是一种基于XML的图像格式,它允许无限的缩放而不会失去清晰度或质量,这使得SVG在网页设计、移动应用以及需要高分辨率输出的项目中非常流行。与像素基础的图像格式(如JPEG或PNG)不同,SVG是基于路径的,这意味...
使用这样的脚本,用户可以通过调用Photoshop的API来控制软件进行文件操作,如打开PS文件,解析其内容,然后输出为SVG格式。 使用脚本转换PS文件到SVG的优势在于可以批量处理多个文件,自定义转换参数,以及在特定...
SVG路径是由一系列坐标点和指令组成的,可以精确地表示出复杂的几何形状。在Photoshop中,形状图层包含填充、描边、透明度等属性,这些属性在转换为SVG时都会得到保留。因此,使用save-ps-to-svg1.0.jsx脚本,你可以...
总的来说,"save-ps-to-svg1.0.jsx" 是Photoshop用户的实用工具,它使得在没有第三方插件的情况下也能方便地导出SVG文件,极大地拓宽了Photoshop的输出格式范围,对于需要SVG格式的设计师而言,这是一个非常有价值的...
标题“save-ps-to-svg1.0.zip 让PS支持导出SVG”揭示了一个关于Adobe Photoshop(简称PS)的扩展功能,即添加了将形状图片转换为SVG(Scalable Vector Graphics)路径的能力。SVG是一种基于XML的矢量图像格式,具有...
山武NX-SVG软件包是山武NX-SVG网关设置与调试用的软件,可以做很方便的和其它通讯第三方通讯,省去PLC不少的通讯程序
NN-SVG-master.zip是一个压缩包,包含了用于绘制神经网络架构的SVG图像的工具。这个工具特别之处在于它支持三种不同的神经网络模型的图形表示:全连接神经网络(FCNN)、LeNet以及AlexNet。这些模型在深度学习领域...
赠送jar包:jaxb-svg11-1.0.2.jar; 赠送原API文档:jaxb-svg11-1.0.2-javadoc.jar; 赠送源代码:jaxb-svg11-1.0.2-sources.jar; 赠送Maven依赖信息文件:jaxb-svg11-1.0.2.pom; 包含翻译后的API文档:jaxb-svg11...
react-native-svg-uri, 在来自URL或者 static 文件的本地响应中,呈现SVG图像 react-native-svg-uri从URL或者 static 文件中呈现来自React的SVG图像这是用 rn 0.33和 react-native-svg 4.3.1 ( 依赖于此库) react-...
**jQuery星级评分插件——star-rating-svg详解** 在网页设计中,用户反馈和评价系统是不可或缺的一部分,而星级评分则是最直观、最常见的表现形式。`star-rating-svg`是一款基于jQuery的星级评分插件,它利用SVG...
PhotoShop 将形状图片转化为 svg路径的脚本, 将形状文件转化导出为 SVG路径
HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔动画特效.rar HTML5-SVG超人飞翔...
**前端开源库-babel-plugin-inline-react-svg** 在前端开发中,SVG(Scalable Vector Graphics)因其可伸缩性、良好的浏览器支持以及丰富的图形能力,常被用来创建图标和其他矢量图形。然而,将SVG引入React应用时...
直接 PhotoShop 直接将形状文件转化导出为 SVG路径的脚本
讨论: : 产品特点轻松渲染QR码图像(可选)嵌入徽标安卓的iOS安装安装依赖包yarn add react-native-svg react-native-qrcode-svg 要么npm i -S react-native-svg react-native-qrcode-svg 如果您使用的是React ...