说明
【跟月影学可视化】学习笔记。
方式一:HTML+CSS
优点:方便,不需要第三方依赖,甚至不需要 JavaScript 代码。
缺点:CSS 属性不能直观体现数据,绘制起来也相对麻烦,图形复杂会导致 HTML 元素多,而消耗性能。
方式二:SVG
SVG (Scalable Vector Graphics,可缩放矢量图)是一种基于 XML 语法的图像格式,可以用图片(img 元素)的 src 属性加载。SVG 是对 HTML/CSS 的增强,弥补了 HTML 绘制不规则图形的能力。
优点:简单,直观,方便
缺点:图形复杂时需要的 SVG 元素太多,也非常消耗性能。
方式三:Canvas2D
HTML/CSS 还是 SVG,它们都属于声明式绘图系统,也就是我们根据数据创建各种不同的图形元素(或者 CSS 规则),然后利用浏览器渲染引擎解析它们并渲染出来。Canvas 调用绘图指令,然后引擎直接在页面上绘制图形。这是一种指令式的绘图系统。
优点:高性能绘制
缺点:直接操作图形元素不方便,如果要绘制的图形太多,或者处理大量的像素计算时,Canvas2D 依然会遇到性能瓶颈。
Canvas 能够直接操作绘图上下文,不需要经过 HTML、CSS 解析、构建渲染树、布局等一系列操作。因此单纯绘图的话,Canvas 比 HTML/CSS 和 SVG 要快得多。
另外可以使用 SVG 生成某些图形,然后用 Canvas 来渲染。这样,我们就既可以享受 SVG 的便利性,又可以享受 Canvas 的高性能。
现在浏览器的 canvas 一般有 webgl2、webgl 和 2d 三种上下文。它们并不是一个完整的 canvas api 规范,而是分成了 2d 规范和 webgl 规范。webgl 规范是 opengl es 规范在 web 端的实现,其中 webgl2 对应 opengl es 3.0,而 webgl 对应的是 opengl es 2.0。
方式四:WebGL
优点:功能强大、更适合绘制 3D 场景、大批量绘制、超高性能
缺点:使用繁琐,难度相对较高
使用 WebGL 的情景:
要绘制的图形数量非常多:比如有多达数万个几何图形需要绘制
对较大图像的细节做像素处理:比如,实现物体的光影、流体效果和一些复杂的像素滤镜,需要处理的像素点数量非常的多(一般是数十万甚至上百万数量级的)。
绘制 3D 物体:WebGL 内置了对 3D 物体的投影、深度检测等特性,不需要对坐标做底层的处理。
图形系统与浏览器渲染引擎工作对比
相比于 HTML 和 CSS,Canvas2D 和 WebGL 更适合去做可视化这一领域的绘图工作。它们的绘图 API 能够直接操作绘图上下文,一般不涉及引擎的其他部分,在重绘图像时,也不会发生重新解析文档和构建结构的过程,开销要小很多。
技术选型
几个不错的问题
Canvas 是不是有5M的大小限制?
Canvas画布大小有限制,不同的浏览器不同,一般的可视化大屏足够用了。检测设备的Canvas大小可以用这个项目:https://github.com/jhildenbiddle/canvas-size
Desktop:
Mobile:
canvas2d绘制出来的图形最终也是渲染到gpu中的吧,和webgl渲染到底区别在哪里,为啥webgl性能好
因为 canvas2d 渲染只能由浏览器底层控制,并不能自己控制gpu,而很多优化其实浏览器并不能代替开发者去做。比如说同时绘制几万个小圆形,因为图形都一样,自己写 webgl 的话,可以用 instanced drawing 的方式批量绘制,而 canvas2d 写浏览器不会帮你去这么做。可以说 webgl 在渲染大量元素的时候手段要更多得多,所以性能差别就明显了。
canvas2d 绘图是通过自身的 api,gpu 是浏览器底层调用的,不受开发者控制。webgl 不一样,将数据写入帧缓冲之后,最终通过WebGLProgram 来执行 shader 完成图形渲染,所以 webgl 能够自己控制 gpu 渲染。有很多图形计算,webgl 是可以放在 shader 里面去计算的,这样比用 js 计算快,这就是 gpu 和 cpu 计算的区别。
Canvas绘出圆形颜色渐变的倒计时图形有种朦胧感,怎么回事
这个牵扯到设备像素比dpr了。我们知道 mac 和 iphone 的 dpr 是 2,也就是说一个如果你在这样的设备上绘制 canvas,应当将它的画布坐标设置为样式坐标的 2 倍,才可以清晰地显示图像。浏览器的 window.devicePixelRatio 属性可以读取设备像素比。
实例:不同方式实现饼图
请参考:https://codepen.io/gltjk/pen/vYLmdvJ
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>kiamo pie</title> <style> .pie-graph { 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 ) } </style> </head> <body> <h1>HTML+CSS、SVG、Canvas 三种方式实现饼图</h1> <table> <tr> <th>HTML+CSS</th> <th>SVG</th> <th>Canvas</th> </tr> <tr> <td> <div class="pie-graph"></div> </td> <td> <div id="svg"></div> </td> <td> <div id="canvas"></div> </td> </tr> </table> <script> // 数据处理 function prepare({ values, colors }) { const sum = values.reduce((x, y) => x + y) return values.map((x, i) => [(x / sum) * 2 * Math.PI, colors[i]]) } // svg 绘制饼图 function drawSvgPie(el, { data, center, radius }) { const paths = [] let start = { x: center.x, y: center.y - radius } let deg = 0 for (const [value, color] of data) { deg += value const end = { x: center.x + radius * Math.sin(deg), y: center.y - radius * Math.cos(deg) } const largeArc = value >= Math.PI ? 1 : 0 const pathD = `M ${center.x} ${center.y}` + `L ${start.x} ${start.y}` + `A ${radius} ${radius} 0 ${largeArc} 1 ${end.x} ${end.y}` + 'Z' paths.push(`<path d="${pathD}" fill="${color}" />`) start = end } const d = radius * 2 el.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" ' + `width="${d}px" height="${d}px" viewBox="0 0 ${d} ${d}">` + paths.join('') + '</svg>' } // canvas 绘制饼图 function drawCanvasPie(el, { data, center, radius }) { const canvas = document.createElement('canvas') canvas.setAttribute('width', radius * 2) canvas.setAttribute('height', radius * 2) const ctx = canvas.getContext('2d') let start = -Math.PI / 2 for (const [value, color] of data) { const end = start + value ctx.beginPath() ctx.arc(center.x, center.y, radius, start, end, false) ctx.lineTo(center.x, center.y) ctx.fillStyle = color ctx.fill() ctx.closePath() start = end } el.append(canvas) } // 页面加载完成 window.onload = function () { const values = [30, 35, 45, 90, 160] const colors = ['#37c', '#3c7', 'orange', '#f73', '#ccc'] const commonData = { data: prepare({ values, colors }), center: { x: 125, y: 125 }, radius: 125 } console.log(commonData) drawCanvasPie(document.querySelector('#canvas'), commonData) drawSvgPie(document.querySelector('#svg'), commonData) } </script> </body> </html>