【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;
}

 

 

posted @ 2021-02-03 17:50  ESCAGE  阅读(304)  评论(0编辑  收藏  举报