前端可视化三件套(一) SVG 入门

443 阅读10分钟

1 概述及使用方法

svg是可缩放矢量图形

使用xml格式定义图像

优点:在放大或者改变尺寸的情况下图形质量不会有所损失 属于w3c标准

注:svg图像必须使用.svg后缀来保存

在html中嵌入svg

1 使用 标签 :pluginspage 属性指向下载插件的 URL。

<embed src="rect.svg" width="300" height="100" 
type="image/svg+xml"
pluginspage="http://www.adobe.com/svg/viewer/install/" />

2 使用 标签 codebase 属性指向下载插件的 URL。

<object data="rect.svg" width="300" height="100" 
type="image/svg+xml"
codebase="http://www.adobe.com/svg/viewer/install/" />

3 使用 标签

<iframe src="rect.svg" width="300" height="100">
</iframe>

4 目前最新的已经可以使用svg标签了 SVG2

5 工具:

链接

\

2 SVG内置形状

1 预定义的形状

链接

矩形

fill:填充颜色 ;stroke-width:边框宽度 ; strock 边框颜色 ; x:距离左侧位置 ;y:距离顶部位置;

opacity :透明度;rx 和 ry 属性可使矩形产生圆角

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg  x="20" y="20" width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<rect width="300" height="100"
style="fill:rgb(0,0,255);stroke-width:1;
stroke:rgb(0,0,0);opacity:0.9"/>

</svg>

注:x和y的坐标是矩形左上角坐标

圆形

cx 和 cy 属性定义圆点的 x 和 y 坐标 默认是(0,0) r 属性定义圆的半径。

<circle cx="100" cy="50" r="40" stroke="black"
stroke-width="2" fill="red"/>

椭圆

cx cy 圆点的x坐标和y坐标;rx:水平半径 ry:垂直半径;

多个椭圆重叠的时候后面的会覆盖前面的

<ellipse cx="300" cy="150" rx="200" ry="80"
style="fill:rgb(200,100,50);
stroke:rgb(0,0,100);stroke-width:2"/>

\

线

  • x1在 x 轴定义线条的开始
  • y1 在 y 轴定义线条的开始
  • x2在 x 轴定义线条的结束
  • y2 在 y 轴定义线条的结束
<line x1="0" y1="0" x2="300" y2="300"
style="stroke:rgb(99,99,99);stroke-width:2"/>

折线

使用points定义每个点的坐标 x坐标和y坐标

<polyline points="0,0 0,20 20,20 20,40 40,40 40,60"
style="fill:white;stroke:red;stroke-width:2"/>

多边形

不少于三边的图形: 使用points定义每个角的坐标 下面这个是四边形

<polygon points="220,100   300,210   170,250   123,234"
style="fill:#cccccc;
stroke:#000000;stroke-width:1"/>

路径

最强大的功能

通过d(属性)+参数来实现线的效果 能够划线的只有三个 相对命令可以使用小写

  • M = moveto 从当前点直线移动到某个点 两个参数分别是移动后的坐标 注:M只是移动点不划线 M xy m dx dy
  • L = lineto 画线 从当前点到参数点位置画线段(直线) L x y (or l dx dy)

两个简写: H x (or h dx);水平移动到x点 V y (or v dy) 垂直划线到y点

  • H = horizontal lineto 水平线
  • V = vertical lineto 垂直线到
  • C = curveto 弯曲
  • S = smooth curveto 平滑曲线
  • Q = quadratic Belzier curve 二次Belzier曲线
  • T = smooth quadratic Belzier curveto 光滑二次Belzier曲线
  • A = elliptical Arc 椭圆弧
  • Z = closepath 闭合路径(不区分大小写)
<path d="M250 150 L150 350 L350 350 Z" />
实际上是一个三角形

曲线 弧形

mdn链接

图案

patterns 必须在defs内部使用linearGradient

mdn图案

texts

有两种字体,svg字体和写在图案中的字体

mdn文字

基础变形

g元素 里面可以是一个元素集合 形都会用一个元素的transform属性总结

平移: transform="translate(30,40)"

旋转:transform="rotate(45)"

斜切:skewX skewY

缩放:scale()

复杂:matrix

基础变形

剪切和遮罩

剪切和遮罩

3滤镜 模糊

链接

4 渐变

SVG 渐变必须在 标签中进行定义。 主要有两种渐变:线性渐变和放射性渐变

线性渐变

有两个方向 水平和垂直

水平:

可用来定义 SVG 的线性渐 标签必须嵌套在 的内部。 标签是 definitions 的缩写,它可对诸如渐变之类的特殊元素进行定义。

条件:

y1=y2 ;x1!=x2 水平渐变 ( x不相等就是水平渐变

x1=x2;y1!=y2 垂直渐变( y不相等就是垂直渐变

x1!=x2 y1!=y2 角行渐变

水平渐变:x值不一样

<defs>
<linearGradient id="orange_red" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255,255,0);
stop-opacity:1"/>
<stop offset="100%" style="stop-color:rgb(255,0,0);
stop-opacity:1"/>
</linearGradient>
</defs>

<ellipse cx="200" cy="190" rx="85" ry="55"
style="fill:url(#orange_red)"/>

id是唯一值 需要使用fill:url 获取id连接图形于填充

  • 标签的 x1、x2、y1、y2 属性可定义渐变的开始和结束位置
  • 渐变的颜色范围可由两种或多种颜色组成。每种颜色通过一个 标签来规定。offset 属性用来定义渐变的开始和结束位置。

\

垂直渐变:y轴不一样

<defs>
<linearGradient id="orange_red" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:rgb(255,255,0);
stop-opacity:1"/>
<stop offset="100%" style="stop-color:rgb(255,0,0);
stop-opacity:1"/>
</linearGradient>
</defs>

<ellipse cx="200" cy="190" rx="85" ry="55"
style="fill:url(#orange_red)"/>

反射性渐变

用来定义放射性渐变。 标签必须嵌套在 中

cx、cy 和 r 属性定义外圈,而 fx 和 fy 定义内圈 渐变的颜色范围可由两种或多种颜色组成。每种颜色通过一个 标签来规定。offset 属性用来定义渐变的开始和结束位置。

<defs>
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%"
fx="50%" fy="50%">
<stop offset="0%" style="stop-color:rgb(200,200,200);
stop-opacity:0"/>
<stop offset="100%" style="stop-color:rgb(0,0,255);
stop-opacity:1"/>
</radialGradient>
</defs>

<ellipse cx="230" cy="200" rx="110" ry="100"
style="fill:url(#grey_blue)"/>

\

5 兼容性及使用

1 SVG 2之前,version属性和baseProfile属性用来供其他类型的验证识别SVG的版本。SVG 2不推荐使用version和baseProfile这两个属性

2 作为XML的一种方言,SVG必须正确的绑定命名空间 (在xmlns属性中绑定)。 请阅读命名空间速成 页面获取更多信息。

3 SVG特点是后来居上 后面的会覆盖前面的

4 支持HTML5 的可以直接嵌入SVG

5 如果HTML是XHTML并且声明类型为application/xhtml+xml,可以直接把SVG嵌入到XML源码中。

6 理论上可以使用img 在低于4,0版本的Firefox不支持

7 SVG有两种形式 普通SVG文件是包含SVG标记的简单文本文件

压缩的SVG 后缀是 :.svgz

区别是在上传web文件到服务器时http请求头会多一个 Content-Encoding: gzip 这里可能需要配置一下

Content-Type: image/svg+xml
Vary: Accept-Encoding

\

Content-Type: image/svg+xml
Content-Encoding: gzip
Vary: Accept-Encoding

8 填充颜色大部分使用svg内联样式 也可以使用css

要把background-color、border改成fill和stroke

6 SVG原理

SVG文档中1个像素对应的是输出设备上的一个像素,但是这中情况是可以改变的 这也是SVG为什么可以缩放的原因,

在使用svg的时候可以使用绝对像素和相对像素,也就是说设置宽高的时候是否带单位, 如果不带单位 那么就会使用用户的单位,

在默认情况下一个用户单位等于1像素 (可以改变)

<svg width="100" height="100">
<svg width="200" height="200" viewBox="0 0 100 100">

这里定义的画布尺寸是200200px。但是,viewBox属性定义了画布上可以显示的区域:从(0,0)点开始,100宽100高的区域。这个100100的区域,会放到200200的画布上显示。于是就形成了放大两倍的效果。

用户单位和屏幕单位的映射关系被称为用户坐标系统

坐标系统还可以旋转、倾斜、翻转。

默认的用户坐标系统1用户像素等于设备上的1像素(但是设备上可能会自己定义1像素到底是多大)。在定义了具体尺寸单位的SVG中,比如单位是“cm”或“in”,最终图形会以实际大小的1比1比例呈现。

7 SVG拖拽和缩放

transform属性,操作里面的translate和scale就可以了,就是计算方面要用到一些数学变换的知识。

拖拽

首先:可以实现 但是svg元素不能太多 否则会造成卡顿:

完整链接

移动端拖拽和缩放

大图svg拖拽性能优化:

失败:

1,纯svg嵌入html,利用transform属性,结果拖拽卡顿
(这种方案的确是没有加will-change以及开启硬件加速,简直愚蠢hh)
2,canvas+svg,利用drawImage,结果绘制太慢(实时绘制,火焰图里面看到drawImage调用时间最长)
3,svg直接作为img的src,结果CPU占用过高,浏览器卡死

will-change :css实验的一个功能,作用是告知浏览器有那些变化 然后让浏览器提前准备

思路:鼠标按下》鼠标移动》鼠标松开 对应的事件分别是mousedown、mousemove、mouseup

把这三个事件注册到svg元素上

遇到问题:
但是当鼠标拖到svg外面时,在svg外面放开了鼠标,鼠标回到svg中,图形会随着鼠标移动,这样是不应该的,所以应该把最后一个事件mouseup注册到最外面的元素上,那么鼠标在svg外放开也可以触发。

注册两个全局变量

data() {
    return {
        dragging:false,
        mousePos:{x:0, y:0},
    }
}
    

鼠标按下事件

        mousedown(e) {
            const event = window.event || e
            this.dragging = true;
            this.mousePos.x = event .clientX;
            this.mousePos.y = event .clientY;
        },
        //鼠标按下,准备拖拽,记录当前鼠标位置

鼠标移动事件

        mousemove(e) {
            const event = window.event || e
            if(!this.dragging) {
                return ;
            }
            let nx = event .clientX;
            let ny = event .clientY;
            //计算偏移坐标
            let offsetX = nx - this.mousePos.x;
            let offsetY = ny - this.mousePos.y;
            //drag方法会把svg删除再重绘
            this.drag(offsetX, offsetY);
            //继续记录鼠标位置
            this.mousePos.x = nx;
            this.mousePos.y = ny;
        },

最后放开鼠标

        mouseup(e) {
            this.dragging = false;
        },

\

缩放

注意:缩放是根据鼠标滚轮的事件触发,但是鼠标滚轮有他的默认事件,那就是页面滚动,这里要阻止它。

        mousewheel(e) {
            const event = window.event || e
            e.preventDefault();
            let newRadio = this.radio;
            if(e.deltaY > 0) {
                newRadio -= 0.1;
            } else {
                newRadio += 0.1
            }
            if(newRadio <= 0.1) {
                return;
            }
            this.radio = newRadio;
            this.zoom();
        },

\

项目中

我怎么在vue中使用的

本文重点介绍拖拽,单纯实现很简单,但是由于vue项目,机房图有很多事件,拖拽就成了难点

1.获取Svg当前缩放比例--------documen.getElementById("SVG").currentScale

放大:
documen.getElementById("SVG").currentScale = documen.getElementById("SVG").currentScale1.5
**缩小:
**documen.getElementById("SVG").currentScale = documen.getElementById("SVG").currentScale
0.5

项目难点:

嵌入vue项目,貌似selectedElement.setAttributeNS(null, "", "");不好使 使用下面方法替换

1.请尝试绑定方法:
    selectedElement.addEventListener("mousemove", this.moveElement(event), false);
2.记得执行完解绑:
    selectedElement.addEventListener("mouseup", (event) => {
        selectedElement.removeEventListener("mousemove", this.moveElement(event), false);
    }, false);

卡顿

参考

原因:

设置了transition,发现这样设置过渡动画,一旦元素多了后会异常的卡顿,这是因为改变这些属性时会触发浏览器重排大量耗费性能,而transform既不会触发重排也不会触发重绘,是交给gpu渲染,

解决:

1 第三方库

直接使用现有开源库当然是最方便省事的啦,在github中找了几个start比较高的库:svg-pan-zoompanzoom

这两个库都是对g元素的transform来实现缩放的,svg-pan-zoom必须传入svg元素并且会使用一个g元素包裹svg内的所有元素; 而panzoom对传入元素没有限制,但只能对传入的指定元素进行移动缩放

然而本方案有个致命缺点,这两个库都会自动删除svg的viewBox属性,意味着没有办法使用svg的viewBox坐标系了,而这两个库提供的pan和zoom方法都是传入基于Dom坐标系的clientX、clientY,这将影响到现有系统的业务功能(视图中移动到svg图中到指定的元素),所以此方案暂时抛弃

最后 在简单的svg中(没有涉及到业务功能的svg)采用这个库

其他的:

2 window.requestAnimationFrame方法

  • window.requestAnimationFrame可以让DOM在每一帧中集中处理DOM操作,试试这个神器能不能提高性能?
  • 分别在wheelZoom、panning事件中加上requestAnimationFrame,对svg.panzoom.js代码修改如下:
  • 拖动时不会卡顿了,但在浏览大图时整体流畅度依然不高(fps低),并且拖动开始和拖动结束的时候会卡顿一下,

方案3(g transform+requestAnimationFrame+拖动结束修改viewBox)

参考方案1的原理,既然transform性能效果这么好,那么能不能既提高拖拽性能,又不删除svg的viewbox属性呢,想到如下方案:在panning时,仅对指定的g元素(可以把svg所有内容都放进去)transform实现拖动、缩放,在panEnd时将transform变化到svg的viewBox上,再配合window.requestAnimationFrame

方案4(g transform+requestAnimationFrame)

参考方案1的原理,使用一个g元素(代理元素)将svg内所有内容包裹起来,所有的拖动缩放都只影响g元素的transform,不去在panEnd时去设置viewBox了,并且不删除viewBox,可以继续使用svg坐标系

本方案与方案3大体一致,区别就是在panEnd时不再去设置svg的viewBox,因为在方案3中浏览大一点的svg图时方案3在panStart、panEnd时会卡顿,所以我以为是设置viewBox导致浏览器重排/重绘影响了性能(实际上并不是),接着往下看