前言
📫 大家好,我是南木元元,热衷分享有趣实用的文章,希望大家多多支持,一起进步!
🍅 个人主页:南木元元
目录
可视化的含义
浏览器中实现可视化的4种方式
1. HTML+CSS
2. SVG
3. Canvas2D
4. WebGL
技术选型
总结
可视化的含义
说到数据可视化,在很多人的印象里,可视化就等同于制作下面这些美丽的图表。
但其实数据可视化远不止于此。让我们来问问ChatGPT,看看它怎么说。
总结一下,可视化就是:借助于图形化手段,将数据信息组织起来,清晰有效的传达与沟通信息。像我们熟知的图形、图表以及地图等都属于数据可视化的范畴。 在Web上,图形通常是通过浏览器绘制的,本文就来盘点一下在浏览器中渲染绘制图形的几种方式。
浏览器中实现可视化的4种方式
在现代浏览器中实现可视化的绘制技术主要有4种:HTML+CSS、Canvas2D、SVG、WebGL。
1. HTML+CSS
HTML+CSS这种方式通常用来呈现普通的 Web 网页,在可视化中使用的相对较少,但也可以用来实现常见的柱状图、饼图等图表。比如,要实现一个柱状图,其实也很简单,代码如下:
- HTML代码
<div class="bar" style="height: 50px;"></div>
<div class="bar" style="height: 80px;"></div>
<div class="bar" style="height: 120px;"></div>
<div class="bar" style="height: 60px;"></div>
- CSS代码
.bar {
width: 30px;
background-color: red;
display: inline-block;
margin-right: 20px;
}
实现的柱状图效果:
再来看看如何实现一个饼图,代码如下:
.pie {
width: 250px;
height: 250px;
border-radius: 50%;
background: conic-gradient(red 6deg, orange 6deg 18deg, yellow 18deg 45deg, green 45deg 110deg, blue 110deg 200deg, purple 200deg);
}
直接使用conic-gradient方法创建一个锥形渐变即可实现。
通过上面的例子可以发现,一些常见的可视化图表都可以使用HTML和CSS来实现,并且不需要引入额外的第三方库,但为什么在可视化领域很少直接用HTML和CSS来绘制图表呢?这是因为,Web开发以呈现块状内容为主,而可视化开发因为需要呈现各种各样的形状结构,所以,形状更丰富的SVG以及更底层的Canvas2D和WebGL就是更合适的技术了。
此外,使用HTML+CSS实现可视化有2个缺点:
- 在绘制复杂图形时,使用HTML+CSS的能力有限
- 图形发生变化时重绘的性能开销比较大
第一点其实很好理解,HTML和CSS主要还是为网页布局而创造的,使用它们虽然能绘制可视化图表,但是绘制的方式并不简洁。上面实现的柱状图和折线图都是最基础版,如果要实现稍微复杂一点的效果其实就没那么容易了。
第二点,HTML和CSS作为浏览器渲染引擎的一部分,为了完成页面渲染的工作,除了绘制图形外,还要做很多额外的工作。如下是浏览器的渲染过程:
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。
上述过程中,当图形发生变化时,会触发回流和重绘,渲染树中或大或小的部分需要重新计算,这样其实性能开销是很大的。而Canvas2D和WebGL相比而言,它们的绘图API能够直接操作绘图上下文,在重绘图像时,不会发生重新解析文档和构建结构的过程,开销要小很多。
2. SVG
现代浏览器支持SVG(Scalable Vector Graphics,可缩放矢量图),SVG是一种基于XML语法的图像格式,可以用图片(img元素)的src属性加载。而且,浏览器还可以内嵌SVG标签,并且像操作普通的HTML元素一样,利用DOM API 操作SVG元素。下面是使用SVG实现与前面相同的柱状图效果的代码:
<svg width="200" height="200">
<rect x="0" y="80" width="30" height="50" fill="red" />
<rect x="50" y="50" width="30" height="80" fill="red" />
<rect x="100" y="10" width="30" height="120" fill="red" />
<rect x="150" y="70" width="30" height="60" fill="red" />
</svg>
在上述SVG代码中,rect表示绘制一个矩形元素。除了rect外,SVG还提供了丰富的图形元素,可以绘制矩形、圆弧、椭圆、多边形和贝塞尔曲线等等。现在,想要绘制一个饼图,只需使用SVG内置的circle标签,通过stroke-dasharray属性(定义轮廓为虚线)来调整stroke-width(轮廓宽度)就可以实现饼图,代码如下:
<svg viewBox="0 0 32 32" width="100" height="100" style="border-radius: 50%;">
<circle cx="16" cy="16" r="16" stroke-width="32" stroke-dasharray="20 100" fill="yellow" stroke="red"></circle>
<circle cx="16" cy="16" r="16" stroke-width="32" stroke-dasharray="40 100" stroke-dashoffset="-20" fill="transparent" stroke="blue"></circle>
</svg>
实现的饼图效果:
SVG和传统的HTML+CSS的绘图方式差别不大,只不过,HTML元素在绘制矢量图形方面的能力有些不足,而SVG运用了一些SVG支持的特殊属性,恰好弥补了这方面的缺陷。因此,用SVG绘图比用HTML和CSS要方便很多。
但是,SVG图表也有缺点,它HTML元素一样,在输出图形前都需要经过引擎的解析、布局计算和渲染树生成。如果要绘制的图形很复杂,需要大量的SVG元素,就会大大增加DOM树渲染和重绘所需要的时间,对性能开销非常大,所以SVG只适合应用于元素较少的简单可视化场景。
3. Canvas2D
Canvas2D上下文来绘制可视化图表也很方便,它是一种指令式的绘图系统,我们调用一些绘图指令,就可以在画布上绘制图形。而上面的HTML+CSS、SVG其实都属于声明式绘图系统,也就是我们根据数据创建各种不同的图形元素(或者CSS规则),然后利用浏览器渲染引擎解析它们并渲染出来。
我们接下来就继续通过绘制一个上述的柱状图来看看Canvas2D的使用方式,代码如下:
<canvas id="myCanvas" width="200" height="200"></canvas>
<script>
// 获取canvas元素
const canvas = document.getElementById("myCanvas");
// 获取二维渲染上下文
const ctx = canvas.getContext("2d");
// 设置填充颜色
ctx.fillStyle = "red";
// 使用fillRect()绘制填充的矩形
ctx.fillRect(0, 80, 30, 50);
ctx.fillRect(50, 50, 30, 80);
ctx.fillRect(100, 10, 30, 120);
ctx.fillRect(150, 70, 30, 60);
</script>
其实很简单,就是创建一个画布,获取渲染上下文,然后设置填充颜色,最后使用fillRect()绘制相应的填充矩形即可实现简单柱状图( 想要更进一步学习的话,也可以去看我的这篇文章 )。
Canvas的优点是能够直接操作绘图上下文,不需要经过HTML、CSS解析、构建渲染树、布局等一系列操作。因此单纯绘图的话,Canvas比HTML+CSS、SVG要快得多,绘制性能较优。
Canvas的缺点是处理事件交互的时候没有HTML和SVG方便, 在HTML和SVG中,一个元素对应一个基本图形,所以我们可以很方便地操作它们,比如在柱状图的某个柱子上注册点击事件,相比来说,Canvas实现交互就比较麻烦,需要手动去计算图形的坐标位置来获取图形。并且如果要绘制的图形太多,或者处理大量的像素计算时,Canvas2D依然会遇到性能瓶颈。
所以可以总结一下SVG和Canvas2D分别的使用场景:当数据量不大且侧重交互的情况,用SVG比较合适;当数据量较大的时候用Canvas比较合适。
4. WebGL
WebGL是浏览器提供的功能强大的绘图系统,它是基于OpenGL ES 规范的浏览器实现的,API相对更底层,让我们来看看使用WebGL绘制上述的柱状图的代码:
const canvas = document.getElementById("canvas");
//获取WebGL绘图上下文
const gl = canvas.getContext("webgl");
//顶点着色器
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
//片元着色器
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
//创建着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
//创建程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
//获取attribute变量位置
const a_Position = gl.getAttribLocation(program, "a_position");
//向缓冲区对象写入的数据
const vertices = new Float32Array([
-1, -1,
-0.7, -1,
-0.7, -0.5,
-0.7, -0.5,
-1, -0.5,
-1, -1,
-0.5, -1,
-0.2, -1,
-0.2, -0.2,
-0.2, -0.2,
-0.5, -0.2,
-0.5, -1,
0, -1,
0.3, -1,
0.3, 0.2,
0.3, 0.2,
0, 0.2,
0, -1,
0.5, -1,
0.8, -1,
0.8, -0.4,
0.8, -0.4,
0.5, -0.4,
0.5, -1,
])
const vertexBuffer = gl.createBuffer();//创建缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);// 绑定缓冲区对象
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);// 将数据写入缓冲区对象
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0); // 调用顶点缓冲
gl.enableVertexAttribArray(a_Position);// 激活a_Position使用缓冲数组
//绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);
实现的效果:
可以看到,使用WebGL绘制一个简单的柱状图,它的代码相比上面三种方式,就已经复杂了很多,需要手写大量的GLSL代码,学习成本较高,上手比较困难(想要了解更多的可以去看我的这两篇文章:WebGL简介和快速上手WebGL)。
那什么时候需要用到WebGL呢?
- 要绘制的图形数量非常多或要计算的像素点数量非常的多(一般是数十万甚至上百万数量级的)的时候,即使Canvas2D也会达到性能瓶颈,这时就需要用WebGL来绘制,充分利用GPU并行计算的能力,来快速、精准地操作图像的像素,实现超高性能绘制。
- 绘制3D物体。WebGL内置了对 3D 物体的投影、深度检测等处理,所以用它来渲染 3D 物体就不需要我们自己对坐标做底层的处理了。
技术选型
上述四种方式都有各自的优缺点,实际应用中的技术选型可以参考下图:
总结
本文主要介绍了在浏览器中实现可视化的4种方式,不同的方式有各自的优缺点,实际应用中,也应该根据具体的场景来选择合适的方案实现可视化,以达到最佳效果。
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~