作者 | 阿里文娱无线开发专家 波涛
责编 | 夕颜
出品 | CSDN(ID:CSDNnews)
背景介绍
用户在大麦上购票,需要自行选座。在大型场馆下,如何让 10 万+座位绘制达到闪开?这需要技术在绘制上保证性能流程,在选座渲染上通过技术手段赋予更多可能性。因此,大麦引用 SVG 绘制技术,并根据业务场景下作了很多优化,本文是大麦在用户端的技术方案设计与应 用实践。
10 万+座位绘制面临以下挑战
如何丰富标签样式及属性;
SVG 渲染性能优化;
SVG 如何与业务场景结合;
如何将 CSS 能力应用到 SVGKit,保持(iOSAndroidH5)一致性。
大麦 C 端场景下 SVG 应用
1. SVG 介绍
可伸缩矢量图形 (Scalable Vector Graphics),用来定义用于网络的基于矢量的图形,使用 XML 格式定义图形,图像在放大或改变尺寸的情况下其图形质量不会有所损失,是万维网联 盟的标准, DOM 和 XSL 之类的 W3C 标准是一个整体,不失真,兼容现有图片能力前提还 支持矢量(浏览器兼容情况),通过浏览器很早版本支持情况在主流浏览器都支持,SVG 提供的 功能集涵盖了嵌套转换、裁剪路径、Alpha 通道、滤镜效果等能力,它还具备了传统图片没有 的矢量功能,在任何高清设备都很高清。
图 1 SVG 与其他格式图片比较
2. SVGKit 使用
浏览器默认就支持 SVG 渲染,属于 XML-Dom 家族系列,但是在移动端上并没有做原生支 持,还是按照 XML 进行的读取,支持的开源库也不多,在 IOS 上,目前 OC 版本 SvgKit 还不 错,官方 Github 也在继续维护,虽然更新较慢,通过几次 patch 提交 PR 还是很快 merge 的, 一些通用属性和控件支持的不够完善,需要进行定制开发,swift 版本的 macaw 也不错,在动画 效果上更加酷炫,目前也正在做 swift 效果迁移到 OC 中,渲染流程如下:
图 2 SVGKit 渲染加载流程图
1)SVGKit 有哪些标签?
circle = SVGCircleElement; 【圆形】
clipPath = SVGClipPathElement;【层叠路径】 description = SVGDescriptionElement; 【描述】 ellipse = SVGEllipseElement; 【椭圆】
g = SVGGElement; 【容器标签】
line = SVGLineElement;【直线】 path = SVGPathElement;【路径】
polygon = SVGPolygonElement;【多角形】 polyline = SVGPolylineElement;【多边形】 rect = SVGRectElement;【矩形】
svg = SVGSVGElement;【SVG 容器标签】 switch = SVGSwitchElement;【选择】 text = SVGTextElement;【文本】
textArea = TinySVGTextAreaElement;【区域文本】 title = SVGTitleElement;【标题】
2)扩展基于三端统一 SVG 标签和属性
图 3 SVG 标签属性扩展大图
3)SVG 标签在 SVGKit 中渲染流程
a)SVGKit 核心渲染原理分析
视图 SVGKFastImageView.m 加载到窗口显示 核心中心处理类,主要加载 SVG 文件资源文件
类:SVGKimage : NSObject
SVGKParseResult* parsedSVG = [parser parseSynchronously]; // 解析
SVGKImage* finalImage = [[SVGKImage alloc] initWithParsedSVG:parsedSVG
fromSource:source];
b)分析:
解析 SVG-到合成 ViewLayer
初始化 SVGKSource source svg 资源实例 初始化 SVGSVGElement -> DomTree 初始化 SVGDocument -> DomDocument CALayerTree 最终合成的 Layer 树
SVGKParser* parser = [SVGKParser newParserWithDefaultSVGKParserExtensions:source]; 开 始解析
c)解析 XML 类 SVGKParser:NSObject 解析 SVG(XML)文件
+(SVGKParser ) newParserWithDefaultSVGKParserExtensions:(SVGKSource )source (SVGKParseResult*) parseSynchronously. 解析异常处理 XML 解析处理
XML 解析过程 SAX
// 每解析一个 Node 添加到 DOMTree 中. (SVGKParserStyles) SVGKParserDefsAndUse 【解析 useAndDefs 样式】 SVGKParserDOM 【解析 DOM】 SVGKParserGradient【解析渐变标签】 SVGKParserPatternsAndGradients【解析图案】 SVGKParserStyles【解析样式】
SVGKParserSVG【解析 SVG 标签】
d)解析 XML 中 CSS 样式类 SVGKParserStyles :
标签解析到生成 Layer 层 1.类:SVGKParserDOM.m: SVGElement. 2.核心思想:
SVG 标签渲染流程一、SVGKImage.m 渲染 核心思想:遍历 DOM 映射到 iOS layer 绘制
3.生成 UILayer:
-(CALayer *)newCALayerTree
CALayer* newLayerTree = [self newLayerWithElement:self.DOMTree]; CALayer sublayer = [self newLayerWithElement:(SVGElement )child]; [newLayerTree addlayer. Sublayer]
[element layoutLayer:layer]; [layer setNeedsDisplay];
4)SVGKit 分析总结
SVGKit 版本升级 2.X 升级 3.X-Release,升级后主要是一些属性的支持度更完善,包括 Text 富文本渲染,字体多样式支持,还有一些渲染上的优化,可通过 patch 提交 查看,比较一下 W3C 下 SVG 图在 2.X 分支及 3.X 分支的解析及渲染时间,性能能也有提升, 同时增加 image 图片加载 base64 图片,加载在线 URL 及本地资源图片,我们也在 SVGImageElement 中提供扩 展 API 增加 Webp 支持,因为 SVG 本身是为矢量图方案加入 PNG 等图存在一定模糊情况,不 过运营可能会在底图上做一些 Logo 展示,为了减少 SVG 编辑复杂度,做了一些 pngjpg 图的 嵌入,一般图片都不大,以下 API:展示 base64 运营位图片而设计的
[NSData dataContentWithBase64Str:str]
3. 基于 CSS 着色能力
1)为什么用 CSS 着?
SVG 虽然是绘制图形,原理如同 HTML,是给每一个标签设置一个单独 style 好还是通过 CSS Id /class 映射好呢,这个思路和 HTML 处理 STYLE 样式是一样的,便于更改和维护,在 性能上也更好,同时增加了 important 属性,可以更好的配置样式,可做到运营侧根据样式 style 下发方式达到更改 SVG 图效果,可以做到更多活动效果及个性化需求。
2)SVG-CSS 着色渲染过程
SVG 标签基于 CSS 样式快速应用,通过遍历 DomTree,找到对应的 Node 节点,在给 node 节点设置 id 或者 class,然后局部刷新 Tree 父节点,实现换色,细节流程如下
图 4 CSS 着色原理与时序图
3)大麦端选座渲染效果
图 5 CSS 着色渲染效果
4)CSS 着色原理总结及性能比较 如何确定属性使用的是 CSS 颜色还是自带 style 属性?
当 SVG 在解析生成 DomTree 后,我们可以根据 CSSStyle 样式存储的 CSS 样式,给 Node 标签设置 id 及 class,当更改 nodeList 后,相当于树结构进行了修改,在绘制时候查找属性会根 据优先策略 id > class > 进行查找进行属性赋值,我们根据 CSS 属性 !important 来设置最高优 先级,这样就避免了此问题。
端侧渲染流程如下,左侧:是基于 node 遍历后修改,右侧是修改 id/class 方式【推荐】。性能比对:为了兼容 W3C 标准,端上增加了 CSS 特殊属性 important。
图 6 SVG-Codec 总体性能提升对比
4. SVG 约束 DTD
1)背景介绍
当 SVG 生产端在制作 SVG 图,可能会用到 Adobe 等软件,有很多复杂属性及层叠,可能 会产生复杂 XML 格式,这样在渲染过程中会造成大量遍历,影响性能,也有一些特殊属性, 端上并没有支持,例如滤镜、动画,这样,我们就需要有一种约束来校验生产和渲染 SVG 能够 一致。
2)文档类型定义
(DTD)可定义合法的 XML 文档构建模块。它使用一系列合法的元素来定义文档的结构。
DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
DTD 被定义在 xml 的 DOCTYPE 声明中。
3)定义一个名为 note 的 DTD
如果要使用内部定义,则在 xml 文件的 xml 版本声明头下面添加如下代码块:
// DTD 内容]> 如果要引用外部 DTD,那么它应通过下面的语法被封装在 一个 DOCTYPE 定义中: 例如,在 xml 文件的 xml 版本声明头下面添加如下代码块:
一个 DTD 的内容示例:
其中:
!ELEMENT note 定义 note 元素有四个元素:"to、from、heading、body"
!ELEMENT to 定义 to 元素为 "#PCDATA" 类型。
PCDATA 的意思是被解析的字符数据(parsed character data)。可想象为 XML 元素的开始 标签与结束标签之间的文本,PCDATA 是会被解析器解析的文本。这些文本将被解析器检查以 及标记,文本中的标签会被当作标记来处理,而实体会被展开,不过,被解析的字符数据不应 当包含任何 &、< 或者 > 字符;需要使用 &、< 以及 > 实体来分别替换它们。
4)例如在 DTD 中声明
它表示在和之间可以插入字符或者子标 签,CDATA 的意思是字符数据(character data, CDATA 是不会被解析器解析的文本。在这些文 本中的标签不会被当作标记来对待,其中的实体也不会被展开。
5)如何校验
在完成 DTD 文件的编写后,就是使用 DTD 了,1、 一般使用代码解析的方式,进行 DTD 对 xml 的规范性校验,首先在 svg 的头部加入:然后 在解析 svg 时,声明合法性校验例如,以 SAX 解析 XML 为例:
SAXParserFactory spf = SAXParserFactory.newInstance; spf.setValidating(true); // 关键设置
SAXParser sp = spf.newSAXParser; XMLReader xr = sp.getXMLReader;
XMLParser.SAXHandler handler = new XMLParser.SAXHandler; xr.setContentHandler(handler);
xr.setErrorHandler(new SAXErrorHandler); // 输出校验出错的信息
xr.setProperty("http://xml.org/sax/properties/lexical-handler