【GISER&&Painter】svg的那些事
一 什么是SVG
SVG(可缩放矢量图形),基于XML定义的二维图形描述文档。它拥有矢量特征:1)在缩放图像尺寸的过程中,图形质量高保真;2)保持交互性(可编辑/可选/可搜索)3)符合开放标准;
二 SVG和Canvas该选哪个?
2-1 特性优劣分析
下面的内容来自于https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/gg193983(v=vs.85)关于H5中svg和canvas的使用场景选择说明:
【主观因素】
z1)开发人员技术栈,如果开发人员对于图形学了解更多,而并非一个纯粹的web端开发,则有可能会倾向于选择canvas
z2)性能要求:从下图中可以看出,在屏幕范围扩大的变化下,SVG的性能比较稳定,和屏幕尺寸的相关度不强。所以在大屏幕处理非大批量数据时,可以考虑svg技术。但如果时在渲染要素量比较大的时候,就应当考虑canvas技术
【客观因素】
k1)高保真的复杂矢量文档
根据介绍,在Web端进行展示高保真的复杂矢量文档图形的时候,都需要保证在放大或者缩小数据时细节详情无损。一般对于建筑工程图纸/电子航空图纸/组织结构图/地图/生物结构图等场景来说,svg是我们的不二之选。
k2)静态图形细节优势
由于svg本身的保真特性,可以用svg格式替代png格式,作为高清图像加载在Web端。
k3)像素级别的图像处理
如果对于图像有细节处理的需求,例如需要创建复杂的视觉效果,例如三维场景建设/光照特效等。在遇到这些需求的时候,我们需要逐像素处理,这个时候我们会更倾向于选择canvas。
k4)数据的实时更新:在绘制大量的实时数据刷新渲染的场景下,我们应当考虑采用canvas作为渲染基础,因为将svg的渲染要素到DOM,并实现对应的场景效果的开销是远远大于canvas的离屏渲染的。我们利用canvas的成图效果,可以实现大量实时场景的构建和变化,类似于动画片的原理,而非直接操作DOM元素。
k5)图表:由于图表本身有以下几个特点:
a 图表数据是根据现有数据生成的;
b 用户需要和图表进行互动;
c 图表的样式是需要修改的;
综合以上三点,所以图表一般采用SVG创建。
2-2 图表和地图场景下的选择
【图表】
著名的图表组件库Echart中提供了两种渲染方式,SVG和Canvas,可以根据不同的需求进行渲染选择,总体上来说,开发者需要根据软硬件环境、数据量以及功能场景需求。具体可见:https://blog.csdn.net/WuLex/article/details/78828321;
【地图】
写到这里,我就想起了基于webgl的三维地图渲染原理,其中包括了mapbox和cesium这些知名的地图渲染引擎都是选择了canvas画布作为渲染核心,从而去实现复杂的地图需求。这与前面我们提到的高保真SVG构建地图矛盾吗?
当然不矛盾,我还能举出几个以SVG作为渲染技术实现的地图应用或者API,比如OpenLayers早期版本,osm的id编辑器等等,这些都是采用SVG作为渲染底层实现。
那么问题来了,为什么会有这些区别呢?
显而易见,这些选择完全取决于我们的业务场景:
1)对于mapbox/cesium这些三维渲染引擎而言,需要渲染大量的数据构建三维场景,其中场景计算庞大,需要高性能的计算方式(从上文的性能图片就能看出,canvas更适合);
2)对于早期的Open Layers、osm的id编辑器来说,这些API的需求更侧重于前端某些矢量元素的编辑,而非整体场景的展现。而SVG适用于侧重交互/数据量一般的编辑场景。
综上所述,场景需求是决定我们技术方案选型的重要依据。
三 SVG的使用实例
3-1 在html中使用svg
1)svg外部文件【复杂图形】
引入方式:embed/object/iframe
(a) embed(仅适用于h5,可使用脚本):
<embed src="circle1.svg" type="image/svg+xml" />
(b) object(普适,但不可使用脚本):
<object data="circle1.svg" type="image/svg+xml"></object>
(c) iframe(仅适用于h5,可使用脚本):
<iframe src="circle1.svg"></iframe>
2)自定义svg基础矢量图形<svg>标签
矩形/原型/椭圆/直线/多边形/曲线/路径/文本
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"/>
</svg>
3-2 和svg交互操作实现
下图是一个SVG文件实例,从line3可以看到,svg标签中有一个固定属性viewBox,这是我们操作svg变换的主要属性。
我们可以把viewBox理解成为一个相机,我们通过相机镜头去观察图形,如果我们想要让镜头里显示图形rect的全景,我们需要设置镜头和图形之间的距离(假定镜头定焦),这样才能保证镜头视角里能够装下完整的图形rect。
viewBox=“x, y, width, height”,图示如下:
从图中我们能够很容易理解,viewBox其实类似于一个截图窗口,当我们定义的截图窗口截取了部分数据之后,就会将部分数据填充到整个svg范围内;同理,如果viewBox要大于svg元素尺寸时,可以在viewBox中展示img效果。
具体实现代码如下:
// func:获取当前svg的viewBox function getCurrentVB () { let theSvgElement = theSvgObj.getSVGDocument().documentElement; vbCX = parseFloat(theSvgElement.viewBox.animVal.x); vbCY = parseFloat(theSvgElement.viewBox.animVal.y); vbCW = parseFloat(theSvgElement.viewBox.animVal.width); vbCH = parseFloat(theSvgElement.viewBox.animVal.height); } // func:平移svg function svgMouseDown(evt){ getCurrentVB(theSvgObj); panFlag = true; svgStartX = parseInt(evt.clientX); svgStartY = parseInt(evt.clientY); } function svgMouseMove(evt){ if(panFlag){ let theSvgElement = theSvgObj.getSVGDocument().documentElement; theSvgElement.setAttributeNS(null, 'style', 'cursor: move') svgMoveX = parseInt(evt.clientX) - svgStartX; svgMoveY = parseInt(evt.clientY) - svgStartY; vbCX = svgEndX - svgMoveX; vbCY = svgEndX - svgMoveY; theSvgElement.setAttributeNS(null, 'viewBox',vbCX + ' ' + vbCY + ' ' + vbCW + ' ' + vbCH); } } function svgMouseUp(evt){ svgEndX = vbCX; svgEndY = vbCY; panFlag = false; } // func:缩放svg function scrollSvgZoom(e){ // 兼容火狐detail let down = e.wheelDelta?e.wheelDelta < 0:e.detail > 0; if(down){ zoomOut(); }else{ zoomIn(); } } function zoomIn(){ if(zoomW > zoomStepSize * 2 && zoomH > zoomStepSize * 2 ){ zoomVal += zoomStepSize; zoomTo('in'); } } function zoomOut(){ zoomVal -= zoomStepSize; if(zoomVal >= -zoomStepSize*11){ zoomTo('out') }else{ zoomVal = -zoomStepSize * 11; } } function zoomTo(flag){ // 获取最新的viewBox getCurrentVB(); if(flag === 'in'){ zoomX = vbCX + zoomStepSize; zoomY = vbCY + zoomStepSize; zoomW = vbCW - zoomStepSize * 2; zoomH = vbCH - zoomStepSize * 2; }else{ zoomX = vbCX - zoomStepSize; zoomY = vbCY - zoomStepSize; zoomW = vbCW + zoomStepSize * 2; zoomH = vbCH + zoomStepSize * 2; } let theSvgDocument = document.getElementById('svgPic').getSVGDocument(); let theSvgElement = theSvgDocument.documentElement; theSvgElement.setAttributeNS(null, 'viewBox', zoomX+' '+zoomY+' '+zoomW+' '+zoomH); endZoom(); } function endZoom(){ svgEndX = vbCX; svgEndY = vbCY; }