一、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 可視化的能力。