<audio id="audio" title="01 | 浏览器中实现可视化的四种方式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/df/a1/dfc675382eef454f1dc612d18ffdeaa1.mp3"></audio>

你好，我是月影。

上一节课我们了解了什么是可视化。可视化用一句话来说，本质上就是将数据信息组织起来后，以图形的方式呈现出来。在Web上，图形通常是通过浏览器绘制的。现代浏览器是一个复杂的系统，其中负责绘制图形的部分是渲染引擎。渲染引擎绘制图形的方式，我总结了一下，大体上有4种。

第1种是传统的**HTML+CSS**。这种方式通常用来呈现普通的Web网页。

第2种是使用**SVG**。SVG和传统的**HTML+CSS的绘图方式差别不大**。只不过，HTML元素在绘制矢量图形方面的能力有些不足（我们后面会讲到），而SVG恰好弥补了这方面的缺陷。

第3种是使用**Canvas2D**。这是浏览器提供的Canvas API中的其中一种上下文，使用它可以非常方便地绘制出基础的几何图形。在可视化中，Canvas比较常用，下一节课我们会学习它的基本用法。

第4种是使用**WebGL**。这是浏览器提供的Canvas API中的另一种上下文，它是OpenGL ES规范在Web端的实现。我们可以通过它，用GPU渲染各种复杂的2D和3D图形。值得一提的是，WebGL利用了GPU并行处理的特性，这让它在处理大量数据展现的时候，性能大大优于前3种绘图方式。因此，在可视化的应用中，一些数据量大、视觉效果要求高的特殊场景，使用WebGL渲染是一种比较合适的选择。

这4种方式各有利弊，今天我就从宏观层面带你了解这些图形系统，为我们后面更深入的学习打好基础。

## 方式一：HTML+CSS

与传统的Web应用相比，可视化项目，尤其是PC端的可视化大屏展现，不只是使用HTML与CSS相对较少，而且使用方式也不太一样。于是，有些同学就会认为，可视化只能使用SVG、Canvas这些方式，不能使用HTML与CSS。当然了，这个想法是不对。具体的原因是什么呢？我一起来看看。

实际上，现代浏览器的HTML、CSS表现能力很强大，完全可以实现常规的图表展现，比如，我们常见的柱状图、饼图和折线图。

虽然我们后面的课程会主要使用Canvas和WebGL绘图，少数会涉及部分CSS。但是，你可不要觉得它不重要。为啥呢？理由有两个：

- 一些简单的可视化图表，用CSS来实现很有好处，既能简化开发，又不需要引入额外的库，可以节省资源，提高网页打开的速度。
- 理解CSS的绘图思想对于可视化也是很有帮助的，比如，CSS的很多理论就和视觉相关，可视化中都可以拿来借鉴。

所以呢，这一节里我们多讲一点，你一定要好好听。接下来，我们就来说一说，CSS是如何实现常规图表的。

### 1. HTML与CSS是如何实现可视化的？

用CSS实现柱状图其实很简单，原理就是使用网格布局（Grid Layout）加上线性渐变（Linear-gradient），我就不多说了，你可以直接看我这里给出的CSS代码。

<img src="https://static001.geekbang.org/resource/image/68/31/68d43be360923664f2a3d8c2c65fbc31.jpg" alt="" title="用HTML+CSS绘制的柱状图">

```
/**
   dataset = {
     current: [15, 11, 17, 25, 37],
     total: [25, 26, 40, 45, 68],
   }
 */
.bargraph {
  display: grid;
  width: 150px;
  height: 100px;
  padding: 10px;
  transform: scaleY(3);
  grid-template-columns: repeat(5, 20%);
}
.bargraph div {
  margin: 0 2px;
}
.bargraph div:nth-child(1) {
 background: linear-gradient(to bottom, transparent 75%, #37c 0, #37c 85%, #3c7 0);
}
.bargraph div:nth-child(2) {
 background: linear-gradient(to bottom, transparent 74%, #37c 0, #37c 89%, #3c7 0);
}
.bargraph div:nth-child(3) {
 background: linear-gradient(to bottom, transparent 60%, #37c 0, #37c 83%, #3c7 0);
}
.bargraph div:nth-child(4) {
 background: linear-gradient(to bottom, transparent 55%, #37c 0, #37c 75%, #3c7 0);
}
.bargraph div:nth-child(5) {
 background: linear-gradient(to bottom, transparent 32%, #37c 0, #37c 63%, #3c7 0);
}

```

而要实现饼图，我们可以使用圆锥渐变，方法也很简单，你直接看代码就可以理解。

<img src="https://static001.geekbang.org/resource/image/58/6d/58c6ea6ffce4e47446c0c9636d47226d.jpg" alt="" title="使用圆锥渐变绘制的饼图">

```
.piegraph {
  display: inline-block;
  width: 250px;
  height: 250px;
  border-radius: 50%;
  background-image: conic-gradient(#37c 30deg, #3c7 30deg, #3c7 65deg, orange 65deg, orange 110deg, #f73 110deg, #f73 200deg, #ccc 200deg);
}

```

柱状图和饼图都比较简单，所以我带你快速过了一下。除此之外，我们用HTML和CSS也可以实现折线图。

我们可以用高度很小的Div元素来模拟线段，然后用transform改变角度和位置，这样就能拼成折线图了。 另外，如果使用clip-path这样的高级属性，我们还能实现更复杂的图表，比如，用不同的颜色表示两个不同折线的面积。

<img src="https://static001.geekbang.org/resource/image/cc/c9/cc4d0f6d9260d508758c8043a14ea1c9.jpg" alt="" title="折线图和面积图">

实际上很多常见的可视化图表我们都可以用HTML和CSS来实现，不需要用其他的绘图方式。但是，为什么在可视化领域很少有人直接用HTML和CSS来绘制图表呢？这主要是因为，使用HTML和CSS绘图，有2个缺点。

### 2.用HTML+CSS实现可视化的缺点

首先，HTML和CSS主要还是为网页布局而创造的，使用它们虽然能绘制可视化图表，但是绘制的方式并不简洁。这是因为，从CSS代码里，我们很难看出数据与图形的对应关系，有很多换算也需要开发人员自己来做。这样一来，一旦图表或数据发生改动，就需要我们重新计算，维护起来会很麻烦。

其次，HTML和CSS作为浏览器渲染引擎的一部分，为了完成页面渲染的工作，除了绘制图形外，还要做很多额外的工作。比如说，浏览器的渲染引擎在工作时，要先解析HTML、SVG、CSS，构建DOM树、RenderObject树和RenderLayer树，然后用HTML（或SVG）绘图。当图形发生变化时，我们很可能要重新执行全部的工作，这样的性能开销是非常大的。

而且传统的Web开发，因为涉及UI构建和内容组织，所以这些额外的解析和构建工作都是必须做的。而可视化与传统网页不同，它不太需要复杂的布局，更多的工作是在绘图和数据计算。所以，对于可视化来说，这些额外的工作反而相当于白白消耗了性能。

因此，相比于HTML和CSS，Canvas2D和WebGL更适合去做可视化这一领域的绘图工作。它们的绘图API能够直接操作绘图上下文，一般不涉及引擎的其他部分，在重绘图像时，也不会发生重新解析文档和构建结构的过程，开销要小很多。

<img src="https://static001.geekbang.org/resource/image/d4/9d/d49d2fb673a7fb9f8de329c12fab009d.jpg" alt="" title="图形系统与浏览器渲染引擎工作对比">

## 方式二：SVG

在介绍Canvas2D和WebGL之前，我们先来说一说SVG。现代浏览器支持SVG（Scalable Vector Graphics，可缩放矢量图），SVG是一种基于 XML 语法的图像格式，可以用图片（img元素）的src属性加载。而且，浏览器更强大的是，它还可以内嵌SVG标签，并且像操作普通的HTML元素一样，利用DOM API操作SVG元素。甚至，CSS也可以作用于内嵌的SVG元素。

比如，上面的柱状图，如果用SVG实现的话，我们可以用如下所示的代码来实现：

```
 &lt;!--
      dataset = {
        total: [25, 26, 40, 45, 68],
        current: [15, 11, 17, 25, 37],
      }
  --&gt;
  &lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;120px&quot; height=&quot;240px&quot; viewBox=&quot;0 0 60 100&quot;&gt;
    &lt;g transform=&quot;translate(0, 100) scale(1, -1)&quot;&gt;
      &lt;g&gt;
        &lt;rect x=&quot;1&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;25&quot; fill=&quot;#37c&quot;/&gt;
        &lt;rect x=&quot;13&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;26&quot; fill=&quot;#37c&quot;/&gt;
        &lt;rect x=&quot;25&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;40&quot; fill=&quot;#37c&quot;/&gt;
        &lt;rect x=&quot;37&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;45&quot; fill=&quot;#37c&quot;/&gt;
        &lt;rect x=&quot;49&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;68&quot; fill=&quot;#37c&quot;/&gt;
      &lt;/g&gt;
      &lt;g&gt;
        &lt;rect x=&quot;1&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;15&quot; fill=&quot;#3c7&quot;/&gt;
        &lt;rect x=&quot;13&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;11&quot; fill=&quot;#3c7&quot;/&gt;
        &lt;rect x=&quot;25&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;17&quot; fill=&quot;#3c7&quot;/&gt;
        &lt;rect x=&quot;37&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;25&quot; fill=&quot;#3c7&quot;/&gt;
        &lt;rect x=&quot;49&quot; y=&quot;0&quot; width=&quot;10&quot; height=&quot;37&quot; fill=&quot;#3c7&quot;/&gt;
      &lt;/g&gt;
    &lt;/g&gt;
  &lt;/svg&gt;

```

从上面的SVG代码中，我们可以一目了然地看出，数据total和current分别对应SVG中两个g元素下的rect元素的高度。也就是说，元素的属性和数值可以直接对应起来。而CSS代码并不能直观体现出数据的数值，需要进行CSS规则转换。具体如下图所示：

<img src="https://static001.geekbang.org/resource/image/c9/62/c9716f8c3768a2384c3baf5f8ec87362.jpg" alt="">

在上面这段SVG代码中，g表示分组，rect表示绘制一个矩形元素。除了rect外，SVG还提供了丰富的图形元素，可以绘制矩形、圆弧、椭圆、多边形和贝塞尔曲线等等。由于SVG比较复杂，我们会在第4节课专门介绍，如何用SVG绘制可视化图表。在那之前，你也可以通过[MDN官方文档](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial)，来学习更多的SVG的API。

SVG绘制图表与HTML和CSS绘制图表的方式差别不大，只不过是将HTML标签替换成SVG标签，运用了一些SVG支持的特殊属性。

HTML的不足之处在于HTML元素的形状一般是矩形，虽然用CSS辅助，也能够绘制出各种其它形状的图形，甚至不规则图形，但是总体而言还是非常麻烦的。而SVG则弥补了这方面的不足，让不规则图形的绘制变得更简单了。因此，用SVG绘图比用HTML和CSS要便利得多。

但是，SVG图表也有缺点。在渲染引擎中，SVG元素和HTML元素一样，在输出图形前都需要经过引擎的解析、布局计算和渲染树生成。而且，一个SVG元素只表示一种基本图形，如果展示的数据很复杂，生成图形的SVG元素就会很多。这样一来，大量的SVG元素不仅会占用很多内存空间，还会增加引擎、布局计算和渲染树生成的开销，降低性能，减慢渲染速度。这也就注定了SVG只适合应用于元素较少的简单可视化场景。

## 方式三：Canvas2D

除了SVG，使用Canvas2D上下文来绘制可视化图表也很方便，但是在绘制方式上，Canvas2D和HTML/CSS、SVG又有些不同。

无论是使用HTML/CSS还是SVG，它们都属于**声明式**绘图系统，也就是我们根据数据创建各种不同的图形元素（或者CSS规则），然后利用浏览器渲染引擎解析它们并渲染出来。但是Canvas2D不同，它是浏览器提供的一种可以直接用代码在一块平面的“画布”上绘制图形的API，使用它来绘图更像是传统的“编写代码”，简单来说就是调用绘图指令，然后引擎直接在页面上绘制图形。这是一种**指令式**的绘图系统。

那Canvas到底是怎么绘制可视化图表的呢？我们一起来看。

首先，Canvas元素在浏览器上创造一个空白的画布，通过提供渲染上下文，赋予我们绘制内容的能力。然后，我们只需要调用渲染上下文，设置各种属性，然后调用绘图指令完成输出，就能在画布上呈现各种各样的图形了。

为了实现更加复杂的效果，Canvas还提供了非常丰富的设置和绘图API，我们可以通过操作上下文，来改变填充和描边颜色，对画布进行几何变换，调用各种绘图指令，然后将绘制的图形输出到画布上。

总结来说，Canvas能够直接操作绘图上下文，不需要经过HTML、CSS解析、构建渲染树、布局等一系列操作。因此单纯绘图的话，Canvas比HTML/CSS和SVG要快得多。

但是，因为HTML和SVG一个元素对应一个基本图形，所以我们可以很方便地操作它们，比如在柱状图的某个柱子上注册点击事件。而同样的功能在Canvas上就比较难实现了，因为对于Canvas来说，绘制整个柱状图的过程就是一系列指令的执行过程，其中并没有区分“A柱子”、“B柱子”，这让我们很难单独对Canvas绘图的局部进行控制。不过，这并不代表我们就不能控制Canvas的局部了。实际上，通过数学计算我们是可以通过定位的方式来获取局部图形的，在后续的课程中我们会解决这个问题。

这里有一点需要你注意，Canvas和SVG的使用也不是非此即彼的，它们可以结合使用。因为SVG作为一种图形格式，也可以作为image元素绘制到Canvas中。举个例子，我们可以先使用SVG生成某些图形，然后用Canvas来渲染。这样，我们就既可以享受SVG的便利性，又可以享受Canvas的高性能了。

## 方式四：WebGL

WebGL绘制比前三种方式要复杂一些，因为WebGL是基于OpenGL ES规范的浏览器实现的，API相对更底层，使用起来不如前三种那么简单直接。关于WebGL的使用内容，我会在3D篇详细来说。

一般情况下，Canvas2D绘制图形的性能已经足够高了，但是在三种情况下我们有必要直接操作更强大的GPU来实现绘图。

第一种情况，如果我们**要绘制的图形数量非常多**，比如有多达数万个几何图形需要绘制，而且它们的位置和方向都在不停地变化，那我们即使用Canvas2D绘制了，性能还是会达到瓶颈。这个时候，我们就需要使用GPU能力，直接用WebGL来绘制。

第二种情况，如果我们要**对较大图像的细节做像素处理**，比如，实现物体的光影、流体效果和一些复杂的像素滤镜。由于这些效果往往要精准地改变一个图像全局或局部区域的所有像素点，要计算的像素点数量非常的多（一般是数十万甚至上百万数量级的）。这时，即使采用Canvas2D操作，也会达到性能瓶颈，所以我们也要用WebGL来绘制。

第三种情况是**绘制3D物体**。因为WebGL内置了对3D物体的投影、深度检测等特性，所以用它来渲染3D物体就不需要我们自己对坐标做底层的处理了。那在这种情况下，WebGL无论是在使用上还是性能上都有很大优势。

## 要点总结

今天，我们介绍了四种可视化实现方式和它们的优缺点。

HTML+CSS的优点是方便，不需要第三方依赖，甚至不需要JavaScript代码。如果我们要绘制少量常见的图表，可以直接采用HTML和CSS。它的缺点是CSS属性不能直观体现数据，绘制起来也相对麻烦，图形复杂会导致HTML元素多，而消耗性能。

SVG 是对HTML/CSS的增强，弥补了HTML绘制不规则图形的能力。它通过属性设置图形，可以直观地体现数据，使用起来非常方便。但是SVG也有和HTML/CSS同样的问题，图形复杂时需要的SVG元素太多，也非常消耗性能。

Canvas2D 是浏览器提供的简便快捷的指令式图形系统，它通过一些简单的指令就能快速绘制出复杂的图形。由于它直接操作绘图上下文，因此没有HTML/CSS和SVG绘图因为元素多导致消耗性能的问题，性能要比前两者快得多。但是如果要绘制的图形太多，或者处理大量的像素计算时，Canvas2D依然会遇到性能瓶颈。

WebGL 是浏览器提供的功能强大的绘图系统，它使用比较复杂，但是功能强大，能够充分利用GPU并行计算的能力，来快速、精准地操作图像的像素，在同一时间完成数十万或数百万次计算。另外，它还内置了对3D物体的投影、深度检测等处理，这让它更适合绘制3D场景。

知道了这些优缺点，在实际面对可视化需求的时候，我们就可以根据具体项目的特点来选择合适的方案实现可视化了。那具体来说，我们应该怎么选择呢？我这里梳理了一张技术方案的选择图，你可以看一看。

<img src="https://static001.geekbang.org/resource/image/3b/6f/3bf11fcf520504a4e342dd335698c76f.jpg" alt="">

## 小试牛刀

我们在文中实现了SVG版本的柱状图，你可以尝试用SVG实现同HTML/CSS版本一样的饼图、折线图和面积图，体会一下使用SVG实现和HTML/CSS实现的不同点。

另外，下一节课我们会介绍Canvas2D绘制可视化图表，你可以提前预习一下，试一试能否用Canvas2D来绘制文中的柱状图。

欢迎在留言区和我讨论，分享你的答案和思考，也欢迎你把这节课分享给你的朋友，我们下节课见！
