可视化初探上
不写网页的前端工程师,还能做什么
作为前端工程师,很多人的主要工作就是和网页打交道。那扪心自问一下,写了这么多网页之后,你是不是也想要做些尝试或者突破呢?如果是的话,我建议大家试试可视化。
为什么要学可视化
- 很多 C 端或者 B 端的互联网产品都离不开可视化
“双十一”可视化大屏
- 可视化能实现很多传统 Web 网页无法实现的效果
echarts 绘制地图
学习可视化对我们的帮助
学习相关的图形、视觉呈现的原理和方法,能够最大化地丰富我们的知识面,拓宽你的技术成长路线,让你的技术天花板变得更高。更详细点来说,像视觉呈现技术中也有涉及高级 CSS 原理的部分,所以如果你学会了可视化,也会对你的 CSS 技能有很大的启发和提升。
如何学习可视化
浏览器中实现可视化的四种方式
HTML CSS
与传统的 Web 应用相比,可视化项目,尤其是 PC 端的可视化大屏展现,使用 HTML 与 CSS 相对较少,而且使用方式也不太一样。我们可能就会认为,可视化只能使用 SVG、Canvas 这些方式,不能使用 HTML 与 CSS。
实际上,浏览器的 HTML、CSS 表现能力很强大,完全可以实现常规的图表展现,比如,我们常见的柱状图、饼图和折线图。
例如:柱状图和饼状图实现
我们可以用高度很小的 div 元素来模拟线段,然后用 transform 改变角度和位置,这样就能拼成折线图了。 如果使用 clip-path 这样的高级属性,我们还能实现更复杂的图表,比如,用不同的颜色表示两个不同折线的面积。
优点
- 一些简单的可视化图表,用 CSS 来实现很有好处,既能简化开发,又不需要引入额外的库,可以节省资源,提高网页打开的速度
- 理解 CSS 的绘图思想对于可视化也是很有帮助的,比如,CSS 的很多理论就和视觉相关,可视化中都可以拿来借鉴
缺点
- HTML 和 CSS 主要还是为网页布局而创造的,使用它们虽然能绘制可视化图表,但是绘制的方式并不简洁。这是因为,从 CSS 代码里,我们很难看出数据与图形的对应关系,有很多换算也需要开发人员自己来做。这样一来,一旦图表或数据发生改动,就需要我们重新计算,维护起来会很麻烦。
- HTML 和 CSS 作为浏览器渲染引擎的一部分,为了完成页面渲染的工作,除了绘制图形外,还要做很多额外的工作。比如说,浏览器的渲染引擎在工作时,要先解析 HTML、SVG、CSS,构建 DOM 树、RenderObject 树和 RenderLayer 树,然后用 HTML(或 SVG)绘图。当图形发生变化时,我们很可能要重新执行全部的工作,这样的性能开销是非常大的 7.png
因此,相比于 HTML 和 CSS,Canvas2D 和 WebGL 更适合去做可视化这一领域的绘图工作。它们的绘图 API 能够直接操作绘图上下文,一般不涉及引擎的其他部分,在重绘图像时,也不会发生重新解析文档和构建结构的过程,开销要小很多。
SVG
SVG(Scalable Vector Graphics,可缩放矢量图),SVG 是一种基于 XML 语法的图像格式,可以用图片(img 元素)的 src 属性加载。而且,浏览器更强大的是,它还可以内嵌 SVG 标签,并且像操作普通的 HTML 元素一样,利用 DOM API 操作 SVG 元素。甚至,CSS 也可以作用于内嵌的 SVG 元素。
例如:柱状图实现
代码语言:html复制<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="240px" viewBox="0 0 60 100">
<g transform="translate(0, 100) scale(1, -1)">
<g>
<rect x="1" y="0" width="10" height="25" fill="#37c"/>
<rect x="13" y="0" width="10" height="26" fill="#37c"/>
<rect x="25" y="0" width="10" height="40" fill="#37c"/>
<rect x="37" y="0" width="10" height="45" fill="#37c"/>
<rect x="49" y="0" width="10" height="68" fill="#37c"/>
</g>
<g>
<rect x="1" y="0" width="10" height="15" fill="#3c7"/>
<rect x="13" y="0" width="10" height="11" fill="#3c7"/>
<rect x="25" y="0" width="10" height="17" fill="#3c7"/>
<rect x="37" y="0" width="10" height="25" fill="#3c7"/>
<rect x="49" y="0" width="10" height="37" fill="#3c7"/>
</g>
</g>
</svg>
从 SVG 代码中,我们可以一目了然地看出,数据 total 和 current 分别对应 SVG 中两个 g 元素下的 rect 元素的高度。也就是说,元素的属性和数值可以直接对应起来。而 CSS 代码并不能直观体现出数据的数值,需要进行 CSS 规则转换。
在上面这段 SVG 代码中,g 表示分组,rect 表示绘制一个矩形元素。除了 rect 外,SVG 还提供了丰富的图形元素,可以绘制矩形、圆弧、椭圆、多边形和贝塞尔曲线等等。SVG 绘制图表与 HTML 和 CSS 绘制图表的方式差别不大,只不过是将 HTML 标签替换成 SVG 标签,运用了一些 SVG 支持的特殊属性。
优点
HTML 的不足之处在于 HTML 元素的形状一般是矩形,虽然用 CSS 辅助,也能够绘制出各种其它形状的图形,甚至不规则图形,但是总体而言还是非常麻烦的。而 SVG 则弥补了这方面的不足,让不规则图形的绘制变得更简单了。因此,用 SVG 绘图比用 HTML 和 CSS 要便利得多。
缺点
在渲染引擎中,SVG 元素和 HTML 元素一样,在输出图形前都需要经过引擎的解析、布局计算和渲染树生成。而且,一个 SVG 元素只表示一种基本图形,如果展示的数据很复杂,生成图形的 SVG 元素就会很多。这样一来,大量的 SVG 元素不仅会占用很多内存空间,还会增加引擎、布局计算和渲染树生成的开销,降低性能,减慢渲染速度。这也就注定了 SVG 只适合应用于元素较少的简单可视化场景。
Canvas2D
无论是使用 HTML/CSS 还是 SVG,它们都属于声明式
绘图系统,也就是我们根据数据创建各种不同的图形元素(或者 CSS 规则),然后利用浏览器渲染引擎解析它们并渲染出来。但是 Canvas2D 不同,它是浏览器提供的一种可以直接用代码在一块平面的“画布”上绘制图形的 API,使用它来绘图更像是传统的“编写代码”,简单来说就是调用绘图指令,然后引擎直接在页面上绘制图形。这是一种指令式
的绘图系统。(微信小程序支持:2.9.0)
使用
首先,Canvas 元素在浏览器上创造一个空白的画布,通过提供渲染上下文,赋予我们绘制内容的能力。然后,我们只需要调用渲染上下文,设置各种属性,然后调用绘图指令完成输出,就能在画布上呈现各种各样的图形了。
为了实现更加复杂的效果,Canvas 还提供了非常丰富的设置和绘图 API,我们可以通过操作上下文,来改变填充和描边颜色,对画布进行几何变换,调用各种绘图指令,然后将绘制的图形输出到画布上。
例如:绘制正方形
优点
总结来说,Canvas 能够直接操作绘图上下文,不需要经过 HTML、CSS 解析、构建渲染树、布局等一系列操作。因此单纯绘图的话,Canvas 比 HTML/CSS 和 SVG 要快得多。
缺点
因为 HTML 和 SVG 一个元素对应一个基本图形,所以我们可以很方便地操作它们,比如在柱状图的某个柱子上注册点击事件。而同样的功能在 Canvas 上就比较难实现了,因为对于 Canvas 来说,绘制整个柱状图的过程就是一系列指令的执行过程,其中并没有区分“A 柱子”、“B 柱子”,这让我们很难单独对 Canvas 绘图的局部进行控制。
WebGL
WebGL 绘制比前三种方式要复杂一些,因为 WebGL 是基于 OpenGL ES 规范的浏览器实现的,API 相对更底层,使用起来不如前三种那么简单直接。(微信小程序支持:2.7.0)
一般情况下,Canvas2D 绘制图形的性能已经足够高了,但是在三种情况下我们有必要直接操作更强大的 GPU 来实现绘图。
- 如果我们要绘制的图形数量非常多,比如有多达数万个几何图形需要绘制,而且它们的位置和方向都在不停地变化,那我们即使用 Canvas2D 绘制了,性能还是会达到瓶颈。这个时候,我们就需要使用 GPU 能力,直接用 WebGL 来绘制
- 如果我们要对较大图像的细节做像素处理,比如,实现物体的光影、流体效果和一些复杂的像素滤镜。由于这些效果往往要精准地改变一个图像全局或局部区域的所有像素点,要计算的像素点数量非常的多(一般是数十万甚至上百万数量级的)。这时,即使采用 Canvas2D 操作,也会达到性能瓶颈,所以我们也要用 WebGL 来绘制
- 绘制 3D 物体。因为 WebGL 内置了对 3D 物体的投影、深度检测等特性,所以用它来渲染 3D 物体就不需要我们自己对坐标做底层的处理了。那在这种情况下,WebGL 无论是在使用上还是性能上都有很大优势。
对比
用Canvas绘制层次关系图
Canvas 的坐标系
Canvas 的坐标系和浏览器窗口的坐标系类似,它们都默认左上角为坐标原点,x 轴水平向右,y 轴垂直向下。那在我们设置好的画布宽高为 512 * 512 的 Canvas 画布中,它的左上角坐标值为(0,0),右下角坐标值为(512,512) 。这意味着,坐标(0,0)到(512,512)之间的所有图形,都会被浏览器渲染到画布上。
利用 Canvas 绘制几何图形
获取 Canvas 上下文
首先是获取 Canvas 元素。因为 Canvas 元素就是 HTML 文档中的 canvas 标签,所以,我们可以通过 DOM API 获取它,代码如下:
代码语言:javascript复制const canvas = document.querySelector('canvas');
获取了 canvas 元素后,我们就可以通过 getContext 方法拿到它的上下文对象。具体的操作就是,我们调用 canvas.getContext 传入参数 2d。
代码语言:javascript复制const context = canvas.getContext('2d');
用 Canvas 上下文绘制图形
我们拿到的 context 对象上会有许多 API,它们大体上可以分为两类:一类是设置状态的 API,可以设置或改变当前的绘图状态,比如,改变要绘制图形的颜色、线宽、坐标变换等等;另一类是绘制指令 API,用来绘制不同形状的几何图形。
假设我们要在画布的中心位置绘制一个 100 * 100 的红色正方形。
总结
- 获取 Canvas 对象,通过 getContext(‘2d’) 得到 2D 上下文;
- 设置绘图状态,比如填充颜色 fillStyle,平移变换 translate 等等;
- 调用 beginPath 指令开始绘制图形;
- 调用绘图指令,比如 rect,表示绘制矩形;
- 调用 fill 指令,将绘制内容真正输出到画布上。
绘制层次关系图
利用 Canvas 给一组城市数据绘制一个层次关系图了。也就是在一组给出的层次结构数据中,体现出同属于一个省的城市。
数据源:
结果:
canvas arc()
参数 | 描述 |
---|---|
x | 圆的中心的 x 坐标 |
y | 圆的中心的 y 坐标 |
r | 圆的半径 |
sAngle | 起始角,以弧度计。(弧的圆形的三点钟位置是 0 度) |
eAngle | 结束角,以弧度计 |
counterclockwise | 可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针 |
总结
- 首先,Canvas 是一个非常简单易用的图形系统。Canvas 通过一组简单的绘图指令,就能够方便快捷地绘制出各种复杂的几何图形。
- 另外,Canvas 渲染起来相当高效。即使是绘制大量轮廓非常复杂的几何图形,Canvas 也 只需要调用一组简单的绘图指令就能高性能地完成渲染。这个其实和 Canvas 更偏向于渲染层,能够提供底层的图形渲染 API 有关。那在实际实现可视化业务的时候,Canvas 出 色的渲染能力正是它的优势所在。
- 因为 Canvas 在 HTML 层面上是一个独立的画布元素,所以所有 的绘制内容都是在内部通过绘图指令来完成的,绘制出的图形对于浏览器来说,只是 Canvas 中的一个个像素点,我们很难直接抽取其中的图形对象进行操作。比如说,在 HTML 或 SVG 中绘制一系列图形的时候,我们可以一一获取这些图形的元素对象,然后给它们绑定用户事件。但同样的操作在 Canvas 中没有可以实现的简单方法。
用SVG图形元素绘制可视化图表
SVG 的全称是 Scalable Vector Graphics,可缩放矢量图,它是浏览器支持的一种基于 XML 语法的图像格式。因为描述 SVG 的 XML 语言本身和 HTML 非常接近,都是由标签 属性构成的,而且浏览器的 CSS、JavaScript 都能够正常作用于 SVG 元素。我们可以认为,SVG 是 HTML 的增强版。
利用 SVG 绘制几何图形
SVG 属于声明式绘图系统,它的绘制方式和 Canvas 不同,它不需要用 JavaScript 操作绘图指令,只需要和 HTML 一样,声明一些标签就可以实现绘图了。
svg 元素是 SVG 的根元素,属性 xmlns 是 xml 的名字空间。那第一行 代码就表示,svg 元素的 xmlns 属性值是"http://www.w3.org/2000/svg",浏览器根 据这个属性值就能够识别出这是一段 SVG 的内容了。
利用 SVG 绘制层次关系图
我们是使用 document.createElementNS 方法来创建 SVG 元素的。这里你要注意,与使用 document.createElement 方法创建普通的 HTML 元素不 同,SVG 元素要使用 document.createElementNS 方法来创建。
SVG 的 g 元素表示一个分组,我们可以用它来对 SVG 元素建立起层级结构。而且,如果 我们给 g 元素设置属性,那么它的子元素会继承这些属性。
SVG 和 Canvas 的不同点
- 写法上的不同
SVG 是以创建图形元素绘图的“声明式”绘图系统,Canvas 是执行绘图指令绘图的“指令式”绘图系统。
在绘制层次关系图的过程中,SVG 首先通过创建标签来表示图形元素,circle 表示圆,g 表示分组,text 表示文字。接着,SVG 通过元素的 setAttribute 给图形元素赋属性值,这 个和操作 HTML 元素是一样的。
而 Canvas 先是通过上下文执行绘图指令来绘制图形,画圆是调用 context.arc 指令,然后再调用 context.fill 绘制,画文字是调用 context.fillText 指令。另外,Canvas 还通过上下文设置状态属性,context.fillStyle 设置填充颜色,conext.font 设置元素的字体。
- 用户交互实现上的不同
给这个 SVG 版本的层次关系图添加一个功能,也就是当鼠标移动到某个区域时,这个区域会高亮,并且显示出对应的省 - 市信息。
利用 SVG 的 一个图形对应一个 svg 元素的机制,我们就可以像操作普通的 HTML 元素那样,给 svg 元 素添加事件实现用户交互了。所以,SVG 有一个非常大的优点,那就是可以让图形的用户交互非常简单。
和 SVG 相比,利用 Canvas 对图形元素进行用户交互就没有那么容易了。对于圆形的层次关系图来说,在 Canvas 图形上定位鼠标处于哪个圆中并不难,我们只需要计算一下鼠标到每个圆的圆心距离,如果这个距离小于圆的半径,我们就可以确定鼠标在某个圆内部了。如果我们要绘制的图形不是圆、矩形这样的规则图形,而是一个复杂得多的多边形,我们又该怎样确定鼠标在哪个图形元素的内部呢?这对于 Canvas 来说,就是一个 比较复杂的问题了。
绘制大量几何图形时 SVG 的性能问题
虽然使用 SVG 绘图能够很方便地实现用户交互,但是有得必有失,SVG 这个设计给用户交互带来便利性的同时,也带来了局限性。为什么这么说呢?因为它和 DOM 元素一样,以节点的形式呈现在 HTML 文本内容中,依靠浏览器的 DOM 树渲染。如果我们要绘制的图形非常复杂,这些元素节点的数量就会非常多。而节点数量多,就会大大增加 DOM 树渲染和重绘所需要的时间。
就比如说,在绘制如上的层次关系图时,我们只需要绘制数十个节点。但是如果是更复杂的应用,比如我们要绘制数百上千甚至上万个节点,这个时候,DOM 树渲染就会成为性能瓶颈。事实上,在一般情况下,当 SVG 节点超过一千个的时候,你就能很明显感觉到性能问题了。
幸运的是,对于 SVG 的性能问题,我们也是有解决方案的。比如说,我们可以使用虚拟 DOM 方案来尽可能地减少重绘,这样就可以优化 SVG 的渲染。但是这些方案只能解决一 部分问题,当节点数太多时,这些方案也无能为力。这个时候,我们还是得依靠 Canvas 和 WebGL 来绘图,才能彻底解决问题。