一、SVG:可伸缩的矢量图形(声明式方法:保留模式图形绘制)
SVG 本身是基于 XML 的一种独立的数据格式,用于声明式的 2D 矢量图形。但是,它也可以嵌入到 HTML 文档中,这是所有主流浏览器都支持的。
使用 SVG 绘制一个可调整大小的圆:
<html style="height: 100%; width: 100%">
<body style="height: 100%; width: 100%; margin: 0px">
<svg style="height: 100%; width: 100%; display: block" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="25" fill="red" stroke="black"
vector-effect="non-scaling-stroke" />
</svg>
</body>
</html>
当浏览器窗口调整大小或缩放时,它将重新缩放图像,而不会丢失图像的任何质量(因为图像是根据形状定义的,而不是根据像素定义的)。当 SVG 元素被 JavaScript 代码修改时,它还会自动重新绘制图像,这使得 SVG 特别适合与 **JavaScript 库(如 D3)**一起使用,D3 将数据绑定到 DOM 中的元素,从而能够创建从简单图表到更奇特的交互式数据可视化的任何内容。
二、画布canvas(函数式方法:即时模式图形绘制)
canvas 元素只是在网页上提供了一个可以绘图的区域。使用 JavaScript 代码,首先从画布获取上下文,然后使用提供的 API,定义绘制图像的函数。
const canvas = document.getElementById(id);
const context = canvas.getContext(contextType);
// call some methods on context to draw onto the canvas
当脚本执行时,图像立即绘制成了底层位图的像素,浏览器不保留绘制方式的任何信息。为了更新绘图,需要再次执行脚本。重新缩放图像时,也会触发更新绘图,否则,浏览器只会拉伸原始位图,导致图像明显模糊或像素化。
1.上下文:2D
提供了一个用于在画布上绘制 2D 图形的高级 API。
绘制可调整大小的圆:
<html style="height: 100%; width: 100%">
<body style="height: 100%; width: 100%; margin: 0px">
<canvas id="my-canvas" style="height: 100%; width: 100%; display: block"></canvas>
<script>
const canvas = document.getElementById("my-canvas");
const context = canvas.getContext("2d");
function render() {
// Size the drawing surface to match the actual element (no stretch).
canvas.height = canvas.clientHeight;
canvas.width = canvas.clientWidth;
context.beginPath();
// Calculate relative size and position of circle in pixels.
const x = 0.5 * canvas.width;
const y = 0.5 * canvas.height;
const radius = 0.25 * Math.min(canvas.height, canvas.width);
context.arc(x, y, radius, 0, 2 * Math.PI);
context.fillStyle = "red";
context.fill();
context.strokeStyle = "black";
context.stroke();
}
render();
addEventListener("resize", render);
</script>
</body>
</html>
根据画布的当前大小,以像素为单位计算圆的半径和中心位置。这也意味着必须监听缩放的事件并相应地重新绘制。适用于绘制更多对象的、更复杂的动态可视化,比更新 DOM 中的大量元素,并让浏览器来决定何时呈现和呈现什么,带来更好的性能。
2.上下文:WebGL
大多数现代浏览器也支持 webgl 上下文。提供了使用 WebGL 标准绘制硬件加速图形的底层 API,尽管这需要 GPU 支持。它可以用来渲染 2D,更重要的是,也可以用来渲染本篇博客所说的 3D 图形。
//渲染圆圈
<html style="height: 100%; width: 100%">
<body style="height: 100%; width: 100%; margin: 0px">
<canvas id="my-canvas" style="height: 100%; width: 100%; display: block"></canvas>
<script>
const canvas = document.getElementById("my-canvas");
const context = canvas.getContext("webgl");
const redColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
const blackColor = new Float32Array([0.0, 0.0, 0.0, 1.0]);
// Use an orthogonal projection matrix as we're rendering in 2D.
const projectionMatrix = new Float32Array([
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
// Define positions of the vertices of the circle (in clip space).
const radius = 0.5;
const segmentCount = 360;
const positions = [0.0, 0.0];
for (let i = 0; i < segmentCount + 1; i++) {
positions.push(radius * Math.sin(2 * Math.PI * i / segmentCount));
positions.push(radius * Math.cos(2 * Math.PI * i / segmentCount));
}
const positionBuffer = context.createBuffer();
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
context.bufferData(context.ARRAY_BUFFER, new Float32Array(positions), context.STATIC_DRAW);
// Create shaders and program.
const vertexShader = context.createShader(context.VERTEX_SHADER);
context.shaderSource(vertexShader, `
attribute vec4 position;
uniform mat4 projection;
void main() {
gl_Position = projection * position;
}
`);
context.compileShader(vertexShader);
const fragmentShader = context.createShader(context.FRAGMENT_SHADER);
context.shaderSource(fragmentShader, `
uniform lowp vec4 color;
void main() {
gl_FragColor = color;
}
`);
context.compileShader(fragmentShader);
const program = context.createProgram();
context.attachShader(program, vertexShader);
context.attachShader(program, fragmentShader);
context.linkProgram(program);
const positionAttribute = context.getAttribLocation(program, 'position');
const colorUniform = context.getUniformLocation(program, 'color');
const projectionUniform = context.getUniformLocation(program, 'projection');
function render() {
// Size the drawing surface to match the actual element (no stretch).
canvas.height = canvas.clientHeight;
canvas.width = canvas.clientWidth;
context.viewport(0, 0, canvas.width, canvas.height);
context.useProgram(program);
// Scale projection to maintain 1:1 ratio between height and width on canvas.
projectionMatrix[0] = canvas.width > canvas.height ? canvas.height / canvas.width : 1.0;
projectionMatrix[5] = canvas.height > canvas.width ? canvas.width / canvas.height : 1.0;
context.uniformMatrix4fv(projectionUniform, false, projectionMatrix);
const vertexSize = 2;
const vertexCount = positions.length / vertexSize;
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
context.vertexAttribPointer(positionAttribute, vertexSize, context.FLOAT, false, 0, 0);
context.enableVertexAttribArray(positionAttribute);
context.uniform4fv(colorUniform, redColor);
context.drawArrays(context.TRIANGLE_FAN, 0, vertexCount);
context.uniform4fv(colorUniform, blackColor);
context.drawArrays(context.LINE_STRIP, 1, vertexCount - 1);
}
render();
addEventListener("resize", render);
</script>
</body>
</html>
在渲染任何东西之前,要做很多设置。必须使用顶点列表,将圆定义为由小三角形组成的一个序列。还必须定义一个投影矩阵,将3D 模型(一个平面圆)投影到 2D 画布上。然后,必须编写“着色器”(用一种称为 GLSL 的语言),在 GPU 上编译并运行,以确定顶点的位置和颜色。
但是,额外的复杂性和较底层的 API,确实能够更好地控制 2D 图形绘制的性能。还提供了渲染 3D 可视化的能力。